@telemetry-dev/eve 0.1.0

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/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 telemetry.dev
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,145 @@
1
+ # @telemetry-dev/eve
2
+
3
+ Eve telemetry integration for [telemetry.dev](https://telemetry.dev). It wires Vercel's
4
+ [Eve](https://eve.dev) agent framework to telemetry.dev through Eve's native OpenTelemetry,
5
+ lifecycle-hook, and HTTP-client surfaces.
6
+
7
+ ## Install
8
+
9
+ ```sh
10
+ npm install @telemetry-dev/eve eve
11
+ ```
12
+
13
+ Requires `eve >=0.19.0 <1` and Node.js 24 or newer.
14
+
15
+ ## Environment
16
+
17
+ | Variable | Required | Default | Notes |
18
+ | --------------------------- | -------- | ------------------------------ | ----------------------------------------------------------------------- |
19
+ | `TELEMETRY_DEV_API_KEY` | yes | — | Ingest key (`td_live_…`). No key ⇒ the integration is a complete no-op. |
20
+ | `TELEMETRY_DEV_BASE_URL` | no | `https://ingest.telemetry.dev` | Trailing slashes are stripped. |
21
+ | `TELEMETRY_DEV_ENVIRONMENT` | no | `production` | Environment label on every trace/log. |
22
+ | `OTEL_SERVICE_NAME` | no | SDK default | Server instrumentation falls back to the Eve agent name after this env. |
23
+
24
+ All four are also settable through SDK options. Pass SDK options to exactly one entry point in a
25
+ process; whichever initializes first wins. If you only use `telemetryDevHook()` or
26
+ `wrapEveClient()`, pass `serviceName` explicitly or set `OTEL_SERVICE_NAME`.
27
+
28
+ ## Server instrumentation
29
+
30
+ Create `agent/instrumentation.ts`:
31
+
32
+ ```ts
33
+ import { telemetryDevInstrumentation } from "@telemetry-dev/eve";
34
+
35
+ export default telemetryDevInstrumentation();
36
+ ```
37
+
38
+ This registers telemetry.dev as the global OpenTelemetry provider during Eve startup, so Eve's tracer
39
+ scope (`"eve"`) and the AI SDK GenAI tracer scope (`"gen_ai"`) export to telemetry.dev.
40
+
41
+ Useful options:
42
+
43
+ ```ts
44
+ export default telemetryDevInstrumentation({
45
+ functionId: "support-agent",
46
+ recordInputs: false,
47
+ recordOutputs: true,
48
+ runtimeContext: { "team.id": "support" },
49
+ stepStarted(input) {
50
+ return { runtimeContext: { "channel.kind": input.channel.kind } };
51
+ },
52
+ });
53
+ ```
54
+
55
+ `recordInputs` defaults to `captureInput ?? true`; `recordOutputs` defaults to
56
+ `captureOutput ?? true`. Runtime context is attached by Eve to model-call spans under the
57
+ `ai.settings.context.` prefix. The integration also adds `user.id` when Eve auth exposes a principal.
58
+
59
+ ## Lifecycle logs
60
+
61
+ Create `agent/hooks/telemetry-dev.ts`:
62
+
63
+ ```ts
64
+ import { telemetryDevHook } from "@telemetry-dev/eve";
65
+
66
+ export default telemetryDevHook();
67
+ ```
68
+
69
+ The hook emits bounded lifecycle logs for:
70
+
71
+ - session start/completion/failure;
72
+ - turn start/completion/failure;
73
+ - user message receipt without message content;
74
+ - step completion/failure with usage where available;
75
+ - non-completed tool results;
76
+ - HITL input requests and authorization outcomes;
77
+ - subagent calls/completions;
78
+ - compaction requests/completions.
79
+
80
+ Every log includes `gen_ai.conversation.id`, `gen_ai.agent.name`, Eve turn/step ids when present, and
81
+ `eventName` equal to the Eve stream-event type. Hook handlers are fail-open; instrumentation errors are
82
+ routed to `onError` and never thrown back into Eve.
83
+
84
+ ## Client wrapper
85
+
86
+ For apps that call an Eve agent over HTTP:
87
+
88
+ ```ts
89
+ import { wrapEveClient } from "@telemetry-dev/eve";
90
+ import { Client } from "eve/client";
91
+
92
+ const client = wrapEveClient(new Client({ host: "http://127.0.0.1:3000" }), {
93
+ agentName: "support-agent",
94
+ });
95
+
96
+ const session = client.session();
97
+ const response = await session.send("Summarize this incident.");
98
+ const result = await response.result();
99
+ ```
100
+
101
+ `wrapEveClient()` creates one caller-side `invoke_agent` span per `ClientSession.send()` turn. It
102
+ records the outgoing message or HITL input responses as span input, sets `gen_ai.conversation.id` from
103
+ Eve's response session id, streams TTFT from the first message/reasoning delta, accumulates usage from
104
+ `step.completed`, records final message/result output, and marks failures/cancellations as errors.
105
+
106
+ `ClientSession.stream()`, `Client.info()`, and `Client.health()` are intentionally not spanned.
107
+ Attach-streams are unbounded and the probe routes are not agent turns.
108
+
109
+ ## Trace shape
110
+
111
+ A typical turn contains:
112
+
113
+ - Eve server root span `ai.eve.turn`, normalized by telemetry.dev as `invoke_agent`.
114
+ - Nested AI SDK GenAI spans: `chat {modelId}`, `step N`, provider `chat {modelId}` client spans, and
115
+ `execute_tool {toolName}` spans.
116
+ - Usage attributes such as `gen_ai.usage.input_tokens`, `gen_ai.usage.output_tokens`, cache token
117
+ counts, finish reasons, response ids, TTFT, and time-per-output-chunk when Eve's AI SDK integration
118
+ emits them.
119
+ - Runtime context attributes under `ai.settings.context.*`, including Eve session/turn/step/channel
120
+ context and any custom runtime context you return from `step.started`.
121
+ - Lifecycle logs joined to the same session via `gen_ai.conversation.id`.
122
+
123
+ Eve's internal workflow-engine spans (`workflow.execute`, `step.execute`, `world.*`, `hook.resume`,
124
+ etc., tracer scope `workflow`) are excluded by default so traces stay AI-centric. Pass
125
+ `spanFilter: () => true` to export them too.
126
+
127
+ ## Content capture
128
+
129
+ Server-side model content follows Eve's `recordInputs` / `recordOutputs` settings. Caller-side wrapper
130
+ content follows the telemetry.dev SDK `captureInput` / `captureOutput` settings. The SDK mask and
131
+ truncation options apply to captured client-wrapper inputs, outputs, and logs.
132
+
133
+ ## Limitations
134
+
135
+ - Pass options to exactly one entry point in a process. Initialization is one-shot so duplicate entry
136
+ points do not replace the first configuration.
137
+ - `ClientSession.stream()` is not spanned.
138
+ - Cost is computed server-side by telemetry.dev from usage and pricing data; Eve stream events carry
139
+ no cost fields, so the client wrapper records usage only.
140
+ - The client wrapper's `invoke_agent` span ends when the response stream is consumed (iteration or
141
+ `result()`); an unconsumed response leaves the span unexported.
142
+ - Inline subagent child streams (`subagent.event`) are not fanned out in full; the hook logs
143
+ `subagent.started`, `subagent.called`, `subagent.completed`, and child failure events.
144
+ - Eve workflow `$eve.*` run tags are a Vercel-dashboard surface and are not visible to OpenTelemetry,
145
+ so this package does not capture them.
@@ -0,0 +1,37 @@
1
+ import { ClientOverrides, ClientOverrides as ClientOverrides$1, TelemetryOptions } from "@telemetry-dev/sdk";
2
+ import { Client } from "eve/client";
3
+ import { HookDefinition } from "eve/hooks";
4
+ import { InstrumentationDefinition, InstrumentationEvents, InstrumentationRuntimeContext } from "eve/instrumentation";
5
+
6
+ //#region src/config.d.ts
7
+ /** Options accepted by every @telemetry-dev/eve entry point. */
8
+ type TelemetryDevEveOptions = Omit<TelemetryOptions, "registerGlobal">;
9
+ //#endregion
10
+ //#region src/client.d.ts
11
+ interface WrapEveClientOptions extends TelemetryDevEveOptions {
12
+ /** gen_ai.agent.name on turn spans. */
13
+ agentName?: string;
14
+ /** Span name for each send() turn. Default "invoke_agent". */
15
+ spanName?: string;
16
+ }
17
+ declare function wrapEveClient<C extends Client>(client: C, options?: WrapEveClientOptions, overrides?: ClientOverrides): C;
18
+ //#endregion
19
+ //#region src/hook.d.ts
20
+ declare function telemetryDevHook(options?: TelemetryDevEveOptions, overrides?: ClientOverrides$1): HookDefinition;
21
+ //#endregion
22
+ //#region src/instrumentation.d.ts
23
+ interface TelemetryDevInstrumentationOptions extends TelemetryDevEveOptions {
24
+ /** ai.telemetry.functionId override; eve defaults it to the agent name. */
25
+ functionId?: string;
26
+ /** Record model inputs on spans. Default: captureInput ?? true. */
27
+ recordInputs?: boolean;
28
+ /** Record model outputs on spans. Default: captureOutput ?? true. */
29
+ recordOutputs?: boolean;
30
+ /** Static runtime context merged into every model-call span. */
31
+ runtimeContext?: InstrumentationRuntimeContext;
32
+ /** User step.started callback, composed with the integration's own. */
33
+ stepStarted?: InstrumentationEvents["step.started"];
34
+ }
35
+ declare function telemetryDevInstrumentation(options?: TelemetryDevInstrumentationOptions, overrides?: ClientOverrides$1): InstrumentationDefinition;
36
+ //#endregion
37
+ export { type TelemetryDevEveOptions, type TelemetryDevInstrumentationOptions, type WrapEveClientOptions, telemetryDevHook, telemetryDevInstrumentation, wrapEveClient };
package/dist/index.mjs ADDED
@@ -0,0 +1,465 @@
1
+ import { init, log, startSpan } from "@telemetry-dev/sdk";
2
+ import { MessageResponse, isCurrentTurnBoundaryEvent } from "eve/client";
3
+ //#region src/config.ts
4
+ let initialized = false;
5
+ /**
6
+ * Initializes the telemetry.dev SDK exactly once per process for this integration.
7
+ * registerGlobal:true lets eve's tracers (scopes "eve" and "gen_ai") resolve to
8
+ * the SDK provider. spanFilter defaults to exporting everything except eve's
9
+ * internal workflow-engine spans (scope "workflow"); pass spanFilter: () => true
10
+ * to export those too.
11
+ */
12
+ function ensureInit(options = {}, overrides) {
13
+ if (initialized) return;
14
+ initialized = true;
15
+ init({
16
+ ...options,
17
+ registerGlobal: true,
18
+ spanFilter: options.spanFilter ?? ((span) => span.instrumentationScope.name !== "workflow")
19
+ }, overrides);
20
+ }
21
+ //#endregion
22
+ //#region src/client.ts
23
+ function asRecord$1(value) {
24
+ if (value === null || typeof value !== "object") return void 0;
25
+ return value;
26
+ }
27
+ function stringField$1(record, key) {
28
+ const value = record?.[key];
29
+ return typeof value === "string" && value.length > 0 ? value : void 0;
30
+ }
31
+ function numberField$1(record, key) {
32
+ const value = record?.[key];
33
+ return typeof value === "number" ? value : void 0;
34
+ }
35
+ function eventData$1(event) {
36
+ if (!("data" in event)) return {};
37
+ return asRecord$1(event.data) ?? {};
38
+ }
39
+ function eventAttributes(attributes) {
40
+ const out = {};
41
+ for (const [key, value] of Object.entries(attributes)) if (value !== void 0) out[key] = value;
42
+ return out;
43
+ }
44
+ function addUsage(usage, key, value) {
45
+ if (value === void 0) return false;
46
+ usage[key] = (usage[key] ?? 0) + value;
47
+ return true;
48
+ }
49
+ function failureError(code, message) {
50
+ const error = new Error(message ?? code ?? "error");
51
+ if (code) error.name = code;
52
+ return error;
53
+ }
54
+ function inputForSpan(payload) {
55
+ return payload.message ?? payload.inputResponses;
56
+ }
57
+ function bindOrReturn(target, prop) {
58
+ const value = Reflect.get(target, prop, target);
59
+ return typeof value === "function" ? value.bind(target) : value;
60
+ }
61
+ function addLifecycleEvent(span, event) {
62
+ const data = eventData$1(event);
63
+ switch (event.type) {
64
+ case "subagent.called": {
65
+ const remote = asRecord$1(data.remote);
66
+ span.span.addEvent(event.type, eventAttributes({
67
+ "eve.subagent.name": stringField$1(data, "name"),
68
+ "gen_ai.tool.name": stringField$1(data, "toolName"),
69
+ "gen_ai.tool.call.id": stringField$1(data, "callId"),
70
+ "eve.child.session_id": stringField$1(data, "childSessionId"),
71
+ "eve.workflow.id": stringField$1(data, "workflowId"),
72
+ "eve.remote.url": stringField$1(remote, "url")
73
+ }));
74
+ return;
75
+ }
76
+ case "input.requested": {
77
+ const requests = Array.isArray(data.requests) ? data.requests : void 0;
78
+ span.span.addEvent(event.type, eventAttributes({ "eve.input.request_count": requests?.length }));
79
+ return;
80
+ }
81
+ case "authorization.required":
82
+ span.span.addEvent(event.type, eventAttributes({ "eve.authorization.name": stringField$1(data, "name") }));
83
+ return;
84
+ default: return;
85
+ }
86
+ }
87
+ async function* instrumentedStream(response, span, start, payload) {
88
+ const usage = {};
89
+ let sawUsage = false;
90
+ let output;
91
+ let finishReason;
92
+ let timeToFirstChunkMs;
93
+ let error;
94
+ let sawTerminalEvent = false;
95
+ const finish = () => {
96
+ if (!sawTerminalEvent && error === void 0) if (payload.signal?.aborted) {
97
+ error = failureError("cancelled", "cancelled");
98
+ finishReason ??= "cancelled";
99
+ } else {
100
+ error = failureError("stream_incomplete", "stream ended before a terminal event");
101
+ finishReason ??= "error";
102
+ }
103
+ span.end({
104
+ usage: sawUsage ? usage : void 0,
105
+ finishReason,
106
+ output,
107
+ timeToFirstChunkMs,
108
+ error
109
+ });
110
+ };
111
+ try {
112
+ for await (const event of response) {
113
+ const data = eventData$1(event);
114
+ if (isCurrentTurnBoundaryEvent(event)) sawTerminalEvent = true;
115
+ if (timeToFirstChunkMs === void 0 && (event.type === "message.appended" || event.type === "reasoning.appended")) timeToFirstChunkMs = performance.now() - start;
116
+ if (event.type === "step.completed") {
117
+ const eventUsage = asRecord$1(data.usage);
118
+ sawUsage = addUsage(usage, "inputTokens", numberField$1(eventUsage, "inputTokens")) || sawUsage;
119
+ sawUsage = addUsage(usage, "outputTokens", numberField$1(eventUsage, "outputTokens")) || sawUsage;
120
+ sawUsage = addUsage(usage, "cacheReadInputTokens", numberField$1(eventUsage, "cacheReadTokens")) || sawUsage;
121
+ sawUsage = addUsage(usage, "cacheCreationInputTokens", numberField$1(eventUsage, "cacheWriteTokens")) || sawUsage;
122
+ finishReason = stringField$1(data, "finishReason") ?? finishReason;
123
+ }
124
+ if (event.type === "message.completed" && stringField$1(data, "finishReason") !== "tool-calls") output = data.message;
125
+ if (event.type === "result.completed") output = data.result;
126
+ if (event.type === "turn.failed" || event.type === "session.failed" || event.type === "step.failed") {
127
+ if (error === void 0) {
128
+ const code = stringField$1(data, "code");
129
+ error = failureError(code, stringField$1(data, "message"));
130
+ if (code) span.span.setAttribute("error.code", code);
131
+ }
132
+ finishReason = "error";
133
+ }
134
+ addLifecycleEvent(span, event);
135
+ yield event;
136
+ }
137
+ } catch (caught) {
138
+ if (payload.signal?.aborted) {
139
+ error = failureError("cancelled", "cancelled");
140
+ finishReason ??= "cancelled";
141
+ } else error = caught;
142
+ throw caught;
143
+ } finally {
144
+ finish();
145
+ }
146
+ }
147
+ function wrapSession(session, options) {
148
+ return new Proxy(session, { get(target, prop) {
149
+ if (prop === "send") {
150
+ const wrappedSend = async (input) => {
151
+ const payload = typeof input === "string" ? { message: input } : input;
152
+ const span = startSpan(options.spanName ?? "invoke_agent", {
153
+ type: "agent",
154
+ agentName: options.agentName,
155
+ input: inputForSpan(payload)
156
+ });
157
+ const start = performance.now();
158
+ let response;
159
+ try {
160
+ response = await target.send(input);
161
+ } catch (error) {
162
+ if (payload.signal?.aborted) span.end({
163
+ error: failureError("cancelled", "cancelled"),
164
+ finishReason: "cancelled"
165
+ });
166
+ else span.end({ error });
167
+ throw error;
168
+ }
169
+ span.span.setAttribute("gen_ai.conversation.id", response.sessionId);
170
+ return new MessageResponse({
171
+ continuationToken: response.continuationToken,
172
+ sessionId: response.sessionId,
173
+ createStream: () => instrumentedStream(response, span, start, payload)
174
+ });
175
+ };
176
+ return wrappedSend;
177
+ }
178
+ return bindOrReturn(target, prop);
179
+ } });
180
+ }
181
+ function wrapEveClient(client, options = {}, overrides) {
182
+ const { agentName: _agentName, spanName: _spanName, ...sdkOptions } = options;
183
+ ensureInit(sdkOptions, overrides);
184
+ return new Proxy(client, { get(target, prop) {
185
+ if (prop === "session") return (state) => wrapSession(target.session(state), options);
186
+ return bindOrReturn(target, prop);
187
+ } });
188
+ }
189
+ //#endregion
190
+ //#region src/hook.ts
191
+ function asRecord(value) {
192
+ if (value === null || typeof value !== "object") return void 0;
193
+ return value;
194
+ }
195
+ function eventData(event) {
196
+ if (!("data" in event)) return {};
197
+ return asRecord(event.data) ?? {};
198
+ }
199
+ function stringField(record, key) {
200
+ const value = record?.[key];
201
+ return typeof value === "string" && value.length > 0 ? value : void 0;
202
+ }
203
+ function numberField(record, key) {
204
+ const value = record?.[key];
205
+ return typeof value === "number" ? value : void 0;
206
+ }
207
+ function jsonField(record, key) {
208
+ const value = record?.[key];
209
+ if (value === void 0) return void 0;
210
+ try {
211
+ return JSON.stringify(value);
212
+ } catch {
213
+ return;
214
+ }
215
+ }
216
+ function baseAttributes(event, ctx) {
217
+ const data = eventData(event);
218
+ return {
219
+ "gen_ai.conversation.id": ctx.session.id,
220
+ "gen_ai.agent.name": ctx.agent.name,
221
+ "eve.channel.kind": ctx.channel.kind,
222
+ "eve.turn.id": stringField(data, "turnId"),
223
+ "eve.step.index": numberField(data, "stepIndex"),
224
+ "eve.turn.sequence": numberField(data, "sequence")
225
+ };
226
+ }
227
+ function toolAttrs(result) {
228
+ const record = asRecord(result);
229
+ const subagentName = stringField(record, "subagentName");
230
+ return {
231
+ "gen_ai.tool.name": stringField(record, "toolName") ?? (subagentName ? `eve:subagent:${subagentName}` : void 0),
232
+ "gen_ai.tool.call.id": stringField(record, "callId"),
233
+ "eve.subagent.name": subagentName
234
+ };
235
+ }
236
+ function reportError$1(onError, error) {
237
+ try {
238
+ onError?.(error);
239
+ } catch {}
240
+ }
241
+ function emit(event, ctx, level, message, attributes = {}, baseEvent = event) {
242
+ const at = event.meta?.at;
243
+ log(message, {
244
+ level,
245
+ eventName: event.type,
246
+ timestamp: at ? new Date(at) : void 0,
247
+ attributes: {
248
+ ...baseAttributes(baseEvent, ctx),
249
+ ...attributes
250
+ }
251
+ });
252
+ }
253
+ function stepCompletedMessage(data) {
254
+ const finishReason = stringField(data, "finishReason") ?? "unknown";
255
+ const usage = asRecord(data.usage);
256
+ const inputTokens = numberField(usage, "inputTokens");
257
+ const outputTokens = numberField(usage, "outputTokens");
258
+ return `Step completed (${finishReason})${inputTokens !== void 0 && outputTokens !== void 0 ? `: ${inputTokens} in / ${outputTokens} out tokens` : ""}`;
259
+ }
260
+ function logEvent(event, ctx) {
261
+ const data = eventData(event);
262
+ switch (event.type) {
263
+ case "session.started": {
264
+ const runtime = asRecord(data.runtime);
265
+ const invocation = asRecord(data.invocation);
266
+ emit(event, ctx, "info", "Session started", {
267
+ "eve.version": stringField(runtime, "eveVersion"),
268
+ "gen_ai.request.model": stringField(runtime, "modelId"),
269
+ "eve.agent.id": stringField(runtime, "agentId"),
270
+ "eve.parent.session_id": stringField(invocation, "parentSessionId"),
271
+ "eve.parent.call_id": stringField(invocation, "parentCallId"),
272
+ "eve.parent.turn_id": stringField(invocation, "parentTurnId"),
273
+ "eve.subagent.name": stringField(invocation, "name")
274
+ });
275
+ return;
276
+ }
277
+ case "turn.started":
278
+ emit(event, ctx, "debug", "Turn started");
279
+ return;
280
+ case "message.received":
281
+ emit(event, ctx, "debug", "User message received");
282
+ return;
283
+ case "step.completed": {
284
+ const usage = asRecord(data.usage);
285
+ emit(event, ctx, "info", stepCompletedMessage(data), {
286
+ "gen_ai.usage.input_tokens": numberField(usage, "inputTokens"),
287
+ "gen_ai.usage.output_tokens": numberField(usage, "outputTokens"),
288
+ "eve.usage.cache_read_tokens": numberField(usage, "cacheReadTokens"),
289
+ "eve.usage.cache_write_tokens": numberField(usage, "cacheWriteTokens")
290
+ });
291
+ return;
292
+ }
293
+ case "step.failed":
294
+ emit(event, ctx, "error", `Step failed: ${stringField(data, "message") ?? ""}`, {
295
+ "error.code": stringField(data, "code"),
296
+ "eve.error.details": jsonField(data, "details")
297
+ });
298
+ return;
299
+ case "action.result": {
300
+ const status = stringField(data, "status");
301
+ if (status === "completed") return;
302
+ const error = asRecord(data.error);
303
+ emit(event, ctx, status === "failed" ? "error" : "warn", `Tool ${status ?? "failed"}: ${stringField(error, "message") ?? ""}`, {
304
+ "error.code": stringField(error, "code"),
305
+ ...toolAttrs(data.result)
306
+ });
307
+ return;
308
+ }
309
+ case "input.requested":
310
+ emit(event, ctx, "info", "Input requested (HITL)", { "eve.input.request_count": (Array.isArray(data.requests) ? data.requests : void 0)?.length });
311
+ return;
312
+ case "authorization.required":
313
+ emit(event, ctx, "warn", `Authorization required: ${stringField(data, "name") ?? ""}`, { "eve.authorization.name": stringField(data, "name") });
314
+ return;
315
+ case "authorization.completed": {
316
+ const outcome = stringField(data, "outcome");
317
+ emit(event, ctx, outcome === "authorized" ? "info" : "warn", `Authorization ${outcome ?? "completed"}: ${stringField(data, "name") ?? ""}`, {
318
+ "eve.authorization.name": stringField(data, "name"),
319
+ "eve.authorization.outcome": outcome,
320
+ "eve.authorization.reason": stringField(data, "reason")
321
+ });
322
+ return;
323
+ }
324
+ case "subagent.started":
325
+ emit(event, ctx, "info", "Subagent started", {
326
+ "eve.subagent.name": stringField(data, "subagentName"),
327
+ "gen_ai.tool.call.id": stringField(data, "callId")
328
+ });
329
+ return;
330
+ case "subagent.event": {
331
+ const child = asRecord(data.event);
332
+ const childData = asRecord(child?.data);
333
+ const childType = stringField(child, "type");
334
+ const attrs = {
335
+ "eve.subagent.name": stringField(data, "subagentName"),
336
+ "gen_ai.tool.call.id": stringField(data, "callId")
337
+ };
338
+ if (childType === "step.failed") {
339
+ emit(event, ctx, "error", `Subagent step failed: ${stringField(childData, "message") ?? ""}`, {
340
+ ...attrs,
341
+ "error.code": stringField(childData, "code"),
342
+ "eve.error.details": jsonField(childData, "details")
343
+ }, child);
344
+ return;
345
+ }
346
+ if (childType === "turn.failed") {
347
+ emit(event, ctx, "error", `Subagent turn failed: ${stringField(childData, "message") ?? ""}`, {
348
+ ...attrs,
349
+ "error.code": stringField(childData, "code"),
350
+ "eve.error.details": jsonField(childData, "details")
351
+ }, child);
352
+ return;
353
+ }
354
+ if (childType === "session.failed") emit(event, ctx, "error", `Subagent session failed: ${stringField(childData, "message") ?? ""}`, {
355
+ ...attrs,
356
+ "error.code": stringField(childData, "code"),
357
+ "eve.error.details": jsonField(childData, "details")
358
+ }, child);
359
+ return;
360
+ }
361
+ case "subagent.called": {
362
+ const remote = asRecord(data.remote);
363
+ emit(event, ctx, "info", `Subagent called: ${stringField(data, "name") ?? ""}`, {
364
+ "eve.subagent.name": stringField(data, "name"),
365
+ "gen_ai.tool.name": stringField(data, "toolName"),
366
+ "gen_ai.tool.call.id": stringField(data, "callId"),
367
+ "eve.child.session_id": stringField(data, "childSessionId"),
368
+ "eve.workflow.id": stringField(data, "workflowId"),
369
+ "eve.remote.url": stringField(remote, "url")
370
+ });
371
+ return;
372
+ }
373
+ case "subagent.completed":
374
+ emit(event, ctx, "info", "Subagent completed", {
375
+ "eve.subagent.name": stringField(data, "subagentName"),
376
+ "gen_ai.tool.call.id": stringField(data, "callId")
377
+ });
378
+ return;
379
+ case "compaction.requested":
380
+ emit(event, ctx, "info", `Compaction requested (${stringField(data, "modelId") ?? ""})`, {
381
+ "gen_ai.request.model": stringField(data, "modelId"),
382
+ "gen_ai.usage.input_tokens": numberField(data, "usageInputTokens")
383
+ });
384
+ return;
385
+ case "compaction.completed":
386
+ emit(event, ctx, "debug", "Compaction completed", { "gen_ai.request.model": stringField(data, "modelId") });
387
+ return;
388
+ case "turn.completed":
389
+ emit(event, ctx, "info", "Turn completed");
390
+ return;
391
+ case "turn.failed":
392
+ emit(event, ctx, "error", `Turn failed: ${stringField(data, "message") ?? ""}`, {
393
+ "error.code": stringField(data, "code"),
394
+ "eve.error.details": jsonField(data, "details")
395
+ });
396
+ return;
397
+ case "session.waiting":
398
+ emit(event, ctx, "debug", "Session waiting");
399
+ return;
400
+ case "session.completed":
401
+ emit(event, ctx, "info", "Session completed");
402
+ return;
403
+ case "session.failed":
404
+ emit(event, ctx, "error", `Session failed: ${stringField(data, "message") ?? ""}`, {
405
+ "error.code": stringField(data, "code"),
406
+ "eve.error.details": jsonField(data, "details")
407
+ });
408
+ return;
409
+ default: return;
410
+ }
411
+ }
412
+ function telemetryDevHook(options = {}, overrides) {
413
+ return { events: { "*"(event, ctx) {
414
+ try {
415
+ ensureInit(options, overrides);
416
+ logEvent(event, ctx);
417
+ } catch (error) {
418
+ reportError$1(options.onError, error);
419
+ }
420
+ } } };
421
+ }
422
+ //#endregion
423
+ //#region src/instrumentation.ts
424
+ const envServiceName = () => typeof process !== "undefined" ? process.env.OTEL_SERVICE_NAME : void 0;
425
+ function reportError(onError, error) {
426
+ try {
427
+ onError?.(error);
428
+ } catch {}
429
+ }
430
+ function telemetryDevInstrumentation(options = {}, overrides) {
431
+ const { functionId, recordInputs, recordOutputs, runtimeContext, stepStarted, ...sdkOptions } = options;
432
+ const definition = {
433
+ recordInputs: recordInputs ?? sdkOptions.captureInput ?? true,
434
+ recordOutputs: recordOutputs ?? sdkOptions.captureOutput ?? true,
435
+ setup({ agentName }) {
436
+ try {
437
+ ensureInit({
438
+ ...sdkOptions,
439
+ serviceName: sdkOptions.serviceName ?? envServiceName() ?? agentName
440
+ }, overrides);
441
+ } catch (error) {
442
+ reportError(sdkOptions.onError, error);
443
+ }
444
+ },
445
+ events: { "step.started"(input) {
446
+ const ctx = { ...runtimeContext };
447
+ const userId = input.session.auth.initiator?.principalId ?? input.session.auth.current?.principalId;
448
+ if (userId) ctx["user.id"] = userId;
449
+ try {
450
+ const userResult = stepStarted?.(input);
451
+ if (userResult?.runtimeContext) Object.assign(ctx, userResult.runtimeContext);
452
+ } catch (error) {
453
+ reportError(sdkOptions.onError, error);
454
+ }
455
+ return Object.keys(ctx).length > 0 ? { runtimeContext: ctx } : void 0;
456
+ } }
457
+ };
458
+ if (functionId !== void 0) return {
459
+ ...definition,
460
+ functionId
461
+ };
462
+ return definition;
463
+ }
464
+ //#endregion
465
+ export { telemetryDevHook, telemetryDevInstrumentation, wrapEveClient };