@saguiitay/eventhouse-ai-observability 0.1.1 → 0.2.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.
Files changed (42) hide show
  1. package/CHANGELOG.md +48 -0
  2. package/README.md +159 -43
  3. package/dist/adapters/openai.d.ts +25 -0
  4. package/dist/adapters/vercel.d.ts +25 -0
  5. package/dist/{chunk-XFSF25OP.js → chunk-XGCHW72B.js} +11 -55
  6. package/dist/{chunk-XFSF25OP.js.map → chunk-XGCHW72B.js.map} +1 -1
  7. package/dist/copilot.js +5 -3
  8. package/dist/copilot.js.map +1 -1
  9. package/dist/index.d.ts +1 -6
  10. package/dist/index.js +3 -11
  11. package/dist/openai.d.ts +12 -0
  12. package/dist/openai.js +408 -0
  13. package/dist/openai.js.map +1 -0
  14. package/dist/validate.d.ts +2 -0
  15. package/dist/validate.js +237 -0
  16. package/dist/validate.js.map +1 -0
  17. package/dist/validation/fabric.d.ts +29 -0
  18. package/dist/vercel.d.ts +15 -0
  19. package/dist/vercel.js +369 -0
  20. package/dist/vercel.js.map +1 -0
  21. package/docs/architecture.md +19 -3
  22. package/docs/copilot-adapter.md +1 -1
  23. package/docs/extending.md +45 -1
  24. package/docs/fabric-setup.md +24 -0
  25. package/docs/implementation-report.md +62 -18
  26. package/docs/openai-adapter.md +137 -0
  27. package/docs/quickstart.md +75 -6
  28. package/docs/research-eventstream.md +8 -6
  29. package/docs/research.md +1 -1
  30. package/docs/security.md +1 -1
  31. package/docs/telemetry-schema.md +18 -0
  32. package/docs/vercel-adapter.md +201 -0
  33. package/examples/README.md +85 -5
  34. package/examples/copilot-basic/package.json +1 -1
  35. package/examples/openai-basic/env.example +8 -0
  36. package/examples/openai-basic/index.ts +46 -0
  37. package/examples/shared.ts +3 -62
  38. package/examples/telemetry.ts +124 -0
  39. package/examples/vercel-basic/env.example +8 -0
  40. package/examples/vercel-basic/index.ts +60 -0
  41. package/fabric-setup.zip +0 -0
  42. package/package.json +39 -5
package/dist/vercel.js ADDED
@@ -0,0 +1,369 @@
1
+ import {
2
+ createObservability,
3
+ redactSecrets
4
+ } from "./chunk-XGCHW72B.js";
5
+
6
+ // src/adapters/vercel.ts
7
+ import { AsyncLocalStorage } from "async_hooks";
8
+ import {
9
+ trace,
10
+ INVALID_SPAN_CONTEXT,
11
+ SpanStatusCode
12
+ } from "@opentelemetry/api";
13
+ var operations = /* @__PURE__ */ new Set([
14
+ "invoke_agent",
15
+ "agent_step",
16
+ "chat",
17
+ "execute_tool"
18
+ ]);
19
+ var textKeys = /* @__PURE__ */ new Set([
20
+ "ai.model.provider",
21
+ "ai.model.id",
22
+ "ai.response.id",
23
+ "ai.response.model",
24
+ "ai.response.finishReason",
25
+ "ai.toolCall.name",
26
+ "ai.toolCall.id",
27
+ "gen_ai.system",
28
+ "gen_ai.provider.name",
29
+ "gen_ai.tool.name",
30
+ "gen_ai.tool.call.id",
31
+ "gen_ai.tool.type",
32
+ "gen_ai.request.model",
33
+ "gen_ai.response.model",
34
+ "gen_ai.response.id",
35
+ "gen_ai.response.finish_reasons"
36
+ ]);
37
+ var numberKeys = /* @__PURE__ */ new Set([
38
+ "gen_ai.request.frequency_penalty",
39
+ "gen_ai.request.max_tokens",
40
+ "gen_ai.request.presence_penalty",
41
+ "gen_ai.request.temperature",
42
+ "gen_ai.request.top_k",
43
+ "gen_ai.request.top_p",
44
+ "ai.response.msToFirstChunk",
45
+ "ai.response.msToFinish",
46
+ "ai.response.avgOutputTokensPerSecond",
47
+ "gen_ai.client.operation.duration",
48
+ "gen_ai.client.operation.time_to_first_chunk",
49
+ "gen_ai.client.operation.time_per_output_chunk",
50
+ "gen_ai.execute_tool.duration"
51
+ ]);
52
+ var inputKeys = /* @__PURE__ */ new Set([
53
+ "ai.prompt",
54
+ "ai.prompt.messages",
55
+ "ai.toolCall.args",
56
+ "gen_ai.input.messages",
57
+ "gen_ai.system_instructions",
58
+ "gen_ai.tool.call.arguments"
59
+ ]);
60
+ var outputKeys = /* @__PURE__ */ new Set([
61
+ "ai.response.text",
62
+ "ai.response.reasoning",
63
+ "ai.response.toolCalls",
64
+ "ai.toolCall.result",
65
+ "gen_ai.output.messages",
66
+ "gen_ai.tool.call.result"
67
+ ]);
68
+ var aliases = {
69
+ "ai.model.provider": "gen_ai.provider.name",
70
+ "gen_ai.system": "gen_ai.provider.name",
71
+ "ai.model.id": "gen_ai.request.model",
72
+ "ai.response.id": "gen_ai.response.id",
73
+ "ai.response.model": "gen_ai.response.model",
74
+ "ai.toolCall.name": "gen_ai.tool.name",
75
+ "ai.toolCall.id": "gen_ai.tool.call.id",
76
+ "ai.prompt": "gen_ai.input.messages",
77
+ "ai.prompt.messages": "gen_ai.input.messages",
78
+ "ai.toolCall.args": "gen_ai.tool.arguments",
79
+ "ai.response.text": "gen_ai.output.messages",
80
+ "ai.response.reasoning": "gen_ai.output.reasoning",
81
+ "ai.response.toolCalls": "gen_ai.output.tool_calls",
82
+ "ai.toolCall.result": "gen_ai.tool.result",
83
+ "gen_ai.tool.call.arguments": "gen_ai.tool.arguments",
84
+ "gen_ai.tool.call.result": "gen_ai.tool.result"
85
+ };
86
+ var tokenAliases = {
87
+ "ai.usage.promptTokens": "gen_ai.usage.input_tokens",
88
+ "ai.usage.inputTokens": "gen_ai.usage.input_tokens",
89
+ "ai.usage.completionTokens": "gen_ai.usage.output_tokens",
90
+ "ai.usage.outputTokens": "gen_ai.usage.output_tokens",
91
+ "ai.usage.reasoningTokens": "gen_ai.usage.reasoning.output_tokens",
92
+ "ai.usage.outputTokenDetails.reasoningTokens": "gen_ai.usage.reasoning.output_tokens",
93
+ "ai.usage.cachedInputTokens": "gen_ai.usage.cache_read.input_tokens",
94
+ "ai.usage.inputTokenDetails.cacheReadTokens": "gen_ai.usage.cache_read.input_tokens",
95
+ "ai.usage.inputTokenDetails.cacheWriteTokens": "gen_ai.usage.cache_creation.input_tokens",
96
+ "gen_ai.usage.input_tokens": "gen_ai.usage.input_tokens",
97
+ "gen_ai.usage.output_tokens": "gen_ai.usage.output_tokens",
98
+ "gen_ai.usage.cache_read.input_tokens": "gen_ai.usage.cache_read.input_tokens",
99
+ "gen_ai.usage.cache_creation.input_tokens": "gen_ai.usage.cache_creation.input_tokens",
100
+ "gen_ai.usage.reasoning.output_tokens": "gen_ai.usage.reasoning.output_tokens"
101
+ };
102
+ function attachVercelAI(engine, options) {
103
+ const recordInputs = engine.captureContent && options.recordInputs === true;
104
+ const recordOutputs = engine.captureContent && options.recordOutputs === true;
105
+ let closed = false;
106
+ let shutdownPromise;
107
+ const active = /* @__PURE__ */ new Map();
108
+ const calls = /* @__PURE__ */ new Map();
109
+ const invocation = new AsyncLocalStorage();
110
+ let toolCount = 0;
111
+ function attributes(name, values) {
112
+ const result = {};
113
+ const model = name === "chat";
114
+ for (const [key, value] of Object.entries(values).slice(0, 128)) {
115
+ if (value === void 0) continue;
116
+ const tokenKey = tokenAliases[key];
117
+ if (tokenKey) {
118
+ if (model && typeof value === "number" && Number.isSafeInteger(value) && value >= 0)
119
+ result[tokenKey] = value;
120
+ continue;
121
+ }
122
+ if (numberKeys.has(key)) {
123
+ if (typeof value === "number" && Number.isFinite(value))
124
+ result[key] = value;
125
+ continue;
126
+ }
127
+ const content = recordInputs && inputKeys.has(key) || recordOutputs && outputKeys.has(key);
128
+ if (!textKeys.has(key) && !content) continue;
129
+ const clean = (item) => typeof item === "string" ? redactSecrets(item).slice(0, 4096) : void 0;
130
+ const cleaned = Array.isArray(value) ? value.slice(0, 16).filter((v) => typeof v === "string").map((v) => redactSecrets(v).slice(0, 4096)) : clean(value);
131
+ if (cleaned !== void 0) result[aliases[key] ?? key] = cleaned;
132
+ }
133
+ return result;
134
+ }
135
+ function startSpan(_requestedName, spanOptions = {}, parent) {
136
+ if (closed) return trace.wrapSpanContext(INVALID_SPAN_CONTEXT);
137
+ const operation = spanOptions.attributes?.["gen_ai.operation.name"];
138
+ const name = typeof operation === "string" && operations.has(operation) ? operation : "ai.operation";
139
+ const model = name === "chat";
140
+ const span = engine.startSpan(name, {
141
+ ...spanOptions.kind !== void 0 ? { kind: spanOptions.kind } : {},
142
+ ...spanOptions.startTime !== void 0 ? { startTime: spanOptions.startTime } : {},
143
+ // An empty SDK context must not replace the engine's local async parent.
144
+ ...parent && trace.getSpanContext(parent) ? { parent } : {},
145
+ attributes: {
146
+ "vercel.span.type": model ? "model_request" : name === "execute_tool" ? "tool" : name === "agent_step" ? "step" : "turn",
147
+ "gen_ai.operation.name": name,
148
+ ...attributes(name, spanOptions.attributes ?? {})
149
+ }
150
+ });
151
+ let ended = false;
152
+ const wrapped = {
153
+ spanContext: () => span.spanContext(),
154
+ isRecording: () => !closed && !ended && span.isRecording(),
155
+ setAttribute(key, value) {
156
+ return wrapped.setAttributes({ [key]: value });
157
+ },
158
+ setAttributes(values) {
159
+ if (!closed && !ended) {
160
+ span.setAttributes(attributes(name, values));
161
+ if (values["ai.response.finishReason"] === "error" || Array.isArray(values["gen_ai.response.finish_reasons"]) && values["gen_ai.response.finish_reasons"].includes("error")) {
162
+ span.setStatus({ code: SpanStatusCode.ERROR });
163
+ span.setAttribute("error.type", "provider_error");
164
+ }
165
+ }
166
+ return wrapped;
167
+ },
168
+ addEvent() {
169
+ return wrapped;
170
+ },
171
+ addLink() {
172
+ return wrapped;
173
+ },
174
+ addLinks() {
175
+ return wrapped;
176
+ },
177
+ setStatus(status) {
178
+ if (!closed && !ended) {
179
+ span.setStatus({ code: status.code });
180
+ if (status.code === SpanStatusCode.ERROR)
181
+ span.setAttribute("error.type", "provider_error");
182
+ }
183
+ return wrapped;
184
+ },
185
+ updateName() {
186
+ return wrapped;
187
+ },
188
+ recordException() {
189
+ if (!closed && !ended)
190
+ span.addEvent("exception", { "exception.type": "provider_error" });
191
+ },
192
+ end(time) {
193
+ if (ended) return;
194
+ ended = true;
195
+ active.delete(wrapped);
196
+ span.end(time);
197
+ }
198
+ };
199
+ if (span.isRecording()) active.set(wrapped, span);
200
+ const current = invocation.getStore();
201
+ const call = current ? calls.get(current.callId) : void 0;
202
+ if (call) {
203
+ if (model) call.model = wrapped;
204
+ if (name === "execute_tool" && current?.toolCallId) {
205
+ if (!call.tools.has(current.toolCallId)) toolCount++;
206
+ call.tools.set(current.toolCallId, wrapped);
207
+ }
208
+ }
209
+ return wrapped;
210
+ }
211
+ const tracer = {
212
+ startSpan,
213
+ startActiveSpan(name, ...args) {
214
+ const callback = args[args.length - 1];
215
+ const settings = args.length > 1 ? args[0] : {};
216
+ const parent = args.length === 3 ? args[1] : void 0;
217
+ const started = startSpan(name, settings, parent);
218
+ return engine.withSpan(started, () => callback(started));
219
+ }
220
+ };
221
+ const native = options.integration(tracer);
222
+ const wrappedIntegration = new Proxy(native, {
223
+ get(target, property, receiver) {
224
+ const method = Reflect.get(target, property, receiver);
225
+ if (typeof method !== "function") return method;
226
+ return (...args) => {
227
+ let event = args[0];
228
+ const callId = event?.callId;
229
+ if (!event || !callId) return Reflect.apply(method, target, args);
230
+ if (closed) {
231
+ return event?.execute?.();
232
+ }
233
+ if (event.error !== void 0 || event.toolOutput?.error !== void 0) {
234
+ const failure = new Error("Provider operation failed");
235
+ failure.name = "ProviderError";
236
+ event = {
237
+ ...event,
238
+ ...event.error !== void 0 ? { error: failure } : {},
239
+ ...event.toolOutput?.error !== void 0 ? { toolOutput: { ...event.toolOutput, error: failure } } : {}
240
+ };
241
+ args = [event, ...args.slice(1)];
242
+ }
243
+ if (property === "onStart") {
244
+ if (calls.size >= engine.maxActiveSpans) return;
245
+ calls.set(callId, { tools: /* @__PURE__ */ new Map() });
246
+ }
247
+ const call = calls.get(callId);
248
+ if (!call) return event?.execute?.();
249
+ if (property === "onLanguageModelCallEnd" && call.model && event.usage) {
250
+ call.model.setAttributes({
251
+ "gen_ai.usage.cache_read.input_tokens": event.usage.inputTokenDetails?.cacheReadTokens,
252
+ "gen_ai.usage.cache_creation.input_tokens": event.usage.inputTokenDetails?.cacheWriteTokens,
253
+ "gen_ai.usage.reasoning.output_tokens": event.usage.outputTokenDetails?.reasoningTokens
254
+ });
255
+ }
256
+ const toolCallId = event.toolCallId ?? event.toolCall?.toolCallId;
257
+ if (property === "onToolExecutionStart" && toolCount >= engine.maxActiveSpans)
258
+ return;
259
+ if (property === "executeLanguageModelCall" || property === "executeTool") {
260
+ const parent = property === "executeTool" ? toolCallId ? call.tools.get(toolCallId) : void 0 : call.model;
261
+ const execute = event.execute;
262
+ const invoke = () => Reflect.apply(method, target, [
263
+ {
264
+ ...event,
265
+ ...parent && execute ? { execute: () => engine.withSpan(parent, execute) } : {}
266
+ },
267
+ ...args.slice(1)
268
+ ]);
269
+ return parent ? engine.withSpan(parent, invoke) : invoke();
270
+ }
271
+ const result = invocation.run(
272
+ { callId, ...toolCallId ? { toolCallId } : {} },
273
+ () => Reflect.apply(method, target, args)
274
+ );
275
+ const terminal = property === "onEnd" || property === "onAbort" || property === "onError";
276
+ if ((property === "onStepEnd" || terminal) && call.model) {
277
+ const span = call.model;
278
+ active.get(span)?.setAttribute("observability.incomplete", true);
279
+ if (event.finishReason === "error" || property === "onAbort" || property === "onError")
280
+ span.setStatus({ code: SpanStatusCode.ERROR });
281
+ span.end(
282
+ typeof event.timestamp === "number" ? new Date(event.timestamp) : void 0
283
+ );
284
+ delete call.model;
285
+ }
286
+ if (property === "onToolExecutionEnd" && toolCallId) {
287
+ if (call.tools.delete(toolCallId)) toolCount--;
288
+ }
289
+ if (property === "onLanguageModelCallEnd") delete call.model;
290
+ if (terminal) {
291
+ for (const span of call.tools.values()) {
292
+ active.get(span)?.setAttribute("observability.incomplete", true);
293
+ span.end();
294
+ }
295
+ toolCount -= call.tools.size;
296
+ calls.delete(callId);
297
+ }
298
+ return result;
299
+ };
300
+ }
301
+ });
302
+ const telemetry = {
303
+ isEnabled: true,
304
+ recordInputs,
305
+ recordOutputs,
306
+ integrations: [wrappedIntegration]
307
+ };
308
+ return {
309
+ telemetry,
310
+ flush: () => engine.flush(),
311
+ getDiagnostics: () => engine.getDiagnostics(),
312
+ shutdown() {
313
+ shutdownPromise ??= (async () => {
314
+ closed = true;
315
+ telemetry.isEnabled = false;
316
+ for (const span of active.values())
317
+ span.setAttribute("observability.incomplete", true);
318
+ if (native.onError) {
319
+ for (const callId of calls.keys()) {
320
+ try {
321
+ Reflect.apply(native.onError, native, [
322
+ { callId, error: new Error("Observation ended") }
323
+ ]);
324
+ } catch {
325
+ }
326
+ }
327
+ }
328
+ for (const [wrapped, span] of active) {
329
+ span.setAttribute("observability.incomplete", true);
330
+ wrapped.end();
331
+ }
332
+ calls.clear();
333
+ toolCount = 0;
334
+ invocation.disable();
335
+ if (options.shutdownEngine) await engine.shutdown();
336
+ else await engine.flush();
337
+ })();
338
+ return shutdownPromise;
339
+ }
340
+ };
341
+ }
342
+
343
+ // src/vercel.ts
344
+ function observeVercelAI(options) {
345
+ if (typeof options?.integration !== "function")
346
+ throw new Error(
347
+ "A native Vercel telemetry integration factory is required"
348
+ );
349
+ if (options.engine) return attachVercelAI(options.engine, options);
350
+ if (options.shutdownEngine === false)
351
+ throw new Error(
352
+ "shutdownEngine: false requires an externally owned engine"
353
+ );
354
+ const engine = createObservability(options);
355
+ try {
356
+ return attachVercelAI(engine, {
357
+ ...options,
358
+ shutdownEngine: true
359
+ });
360
+ } catch (error) {
361
+ void engine.shutdown().catch(() => {
362
+ });
363
+ throw error;
364
+ }
365
+ }
366
+ export {
367
+ observeVercelAI
368
+ };
369
+ //# sourceMappingURL=vercel.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/adapters/vercel.ts","../src/vercel.ts"],"sourcesContent":["import { AsyncLocalStorage } from \"node:async_hooks\";\nimport {\n trace,\n INVALID_SPAN_CONTEXT,\n SpanStatusCode,\n type Attributes,\n type AttributeValue,\n type Context,\n type Span,\n type SpanOptions,\n type Tracer,\n} from \"@opentelemetry/api\";\nimport type { LanguageModelUsage, TelemetryOptions, Telemetry } from \"ai\";\nimport { redactSecrets, type Observability } from \"../engine.js\";\n\nexport type VercelTelemetrySettings = TelemetryOptions;\n\nexport interface VercelAdapterOptions {\n /** Supply the SDK's native integration: tracer => new OpenTelemetry({ tracer }). */\n integration: (tracer: Tracer) => Telemetry;\n /** Also requires captureContent on the engine. Defaults to false. */\n recordInputs?: boolean;\n /** Also requires captureContent on the engine. Defaults to false. */\n recordOutputs?: boolean;\n /** Explicitly transfers engine shutdown ownership. Defaults to false. */\n shutdownEngine?: boolean;\n}\n\nconst operations = new Set([\n \"invoke_agent\",\n \"agent_step\",\n \"chat\",\n \"execute_tool\",\n]);\nconst textKeys = new Set([\n \"ai.model.provider\",\n \"ai.model.id\",\n \"ai.response.id\",\n \"ai.response.model\",\n \"ai.response.finishReason\",\n \"ai.toolCall.name\",\n \"ai.toolCall.id\",\n \"gen_ai.system\",\n \"gen_ai.provider.name\",\n \"gen_ai.tool.name\",\n \"gen_ai.tool.call.id\",\n \"gen_ai.tool.type\",\n \"gen_ai.request.model\",\n \"gen_ai.response.model\",\n \"gen_ai.response.id\",\n \"gen_ai.response.finish_reasons\",\n]);\nconst numberKeys = new Set([\n \"gen_ai.request.frequency_penalty\",\n \"gen_ai.request.max_tokens\",\n \"gen_ai.request.presence_penalty\",\n \"gen_ai.request.temperature\",\n \"gen_ai.request.top_k\",\n \"gen_ai.request.top_p\",\n \"ai.response.msToFirstChunk\",\n \"ai.response.msToFinish\",\n \"ai.response.avgOutputTokensPerSecond\",\n \"gen_ai.client.operation.duration\",\n \"gen_ai.client.operation.time_to_first_chunk\",\n \"gen_ai.client.operation.time_per_output_chunk\",\n \"gen_ai.execute_tool.duration\",\n]);\nconst inputKeys = new Set([\n \"ai.prompt\",\n \"ai.prompt.messages\",\n \"ai.toolCall.args\",\n \"gen_ai.input.messages\",\n \"gen_ai.system_instructions\",\n \"gen_ai.tool.call.arguments\",\n]);\nconst outputKeys = new Set([\n \"ai.response.text\",\n \"ai.response.reasoning\",\n \"ai.response.toolCalls\",\n \"ai.toolCall.result\",\n \"gen_ai.output.messages\",\n \"gen_ai.tool.call.result\",\n]);\nconst aliases: Record<string, string> = {\n \"ai.model.provider\": \"gen_ai.provider.name\",\n \"gen_ai.system\": \"gen_ai.provider.name\",\n \"ai.model.id\": \"gen_ai.request.model\",\n \"ai.response.id\": \"gen_ai.response.id\",\n \"ai.response.model\": \"gen_ai.response.model\",\n \"ai.toolCall.name\": \"gen_ai.tool.name\",\n \"ai.toolCall.id\": \"gen_ai.tool.call.id\",\n \"ai.prompt\": \"gen_ai.input.messages\",\n \"ai.prompt.messages\": \"gen_ai.input.messages\",\n \"ai.toolCall.args\": \"gen_ai.tool.arguments\",\n \"ai.response.text\": \"gen_ai.output.messages\",\n \"ai.response.reasoning\": \"gen_ai.output.reasoning\",\n \"ai.response.toolCalls\": \"gen_ai.output.tool_calls\",\n \"ai.toolCall.result\": \"gen_ai.tool.result\",\n \"gen_ai.tool.call.arguments\": \"gen_ai.tool.arguments\",\n \"gen_ai.tool.call.result\": \"gen_ai.tool.result\",\n};\nconst tokenAliases: Record<string, string> = {\n \"ai.usage.promptTokens\": \"gen_ai.usage.input_tokens\",\n \"ai.usage.inputTokens\": \"gen_ai.usage.input_tokens\",\n \"ai.usage.completionTokens\": \"gen_ai.usage.output_tokens\",\n \"ai.usage.outputTokens\": \"gen_ai.usage.output_tokens\",\n \"ai.usage.reasoningTokens\": \"gen_ai.usage.reasoning.output_tokens\",\n \"ai.usage.outputTokenDetails.reasoningTokens\":\n \"gen_ai.usage.reasoning.output_tokens\",\n \"ai.usage.cachedInputTokens\": \"gen_ai.usage.cache_read.input_tokens\",\n \"ai.usage.inputTokenDetails.cacheReadTokens\":\n \"gen_ai.usage.cache_read.input_tokens\",\n \"ai.usage.inputTokenDetails.cacheWriteTokens\":\n \"gen_ai.usage.cache_creation.input_tokens\",\n \"gen_ai.usage.input_tokens\": \"gen_ai.usage.input_tokens\",\n \"gen_ai.usage.output_tokens\": \"gen_ai.usage.output_tokens\",\n \"gen_ai.usage.cache_read.input_tokens\":\n \"gen_ai.usage.cache_read.input_tokens\",\n \"gen_ai.usage.cache_creation.input_tokens\":\n \"gen_ai.usage.cache_creation.input_tokens\",\n \"gen_ai.usage.reasoning.output_tokens\":\n \"gen_ai.usage.reasoning.output_tokens\",\n};\n\n/**\n * Native SDK instrumentation calls this tracer, not a global provider. Every\n * mutation is filtered before reaching the private engine, including errors\n * and attributes that the SDK's recording flags do not protect.\n */\nexport function attachVercelAI(\n engine: Observability,\n options: VercelAdapterOptions,\n) {\n const recordInputs = engine.captureContent && options.recordInputs === true;\n const recordOutputs = engine.captureContent && options.recordOutputs === true;\n let closed = false;\n let shutdownPromise: Promise<void> | undefined;\n const active = new Map<Span, Span>();\n const calls = new Map<string, { model?: Span; tools: Map<string, Span> }>();\n const invocation = new AsyncLocalStorage<{\n callId: string;\n toolCallId?: string;\n }>();\n let toolCount = 0;\n\n function attributes(name: string, values: Attributes): Attributes {\n const result: Attributes = {};\n const model = name === \"chat\";\n for (const [key, value] of Object.entries(values).slice(0, 128)) {\n if (value === undefined) continue;\n const tokenKey = tokenAliases[key];\n if (tokenKey) {\n if (\n model &&\n typeof value === \"number\" &&\n Number.isSafeInteger(value) &&\n value >= 0\n )\n result[tokenKey] = value;\n continue;\n }\n if (numberKeys.has(key)) {\n if (typeof value === \"number\" && Number.isFinite(value))\n result[key] = value;\n continue;\n }\n const content =\n (recordInputs && inputKeys.has(key)) ||\n (recordOutputs && outputKeys.has(key));\n if (!textKeys.has(key) && !content) continue;\n const clean = (item: AttributeValue): AttributeValue | undefined =>\n typeof item === \"string\"\n ? redactSecrets(item).slice(0, 4096)\n : undefined;\n const cleaned = Array.isArray(value)\n ? value\n .slice(0, 16)\n .filter((v): v is string => typeof v === \"string\")\n .map((v) => redactSecrets(v).slice(0, 4096))\n : clean(value);\n if (cleaned !== undefined) result[aliases[key] ?? key] = cleaned;\n }\n return result;\n }\n\n function startSpan(\n _requestedName: string,\n spanOptions: SpanOptions = {},\n parent?: Context,\n ): Span {\n if (closed) return trace.wrapSpanContext(INVALID_SPAN_CONTEXT);\n const operation = spanOptions.attributes?.[\"gen_ai.operation.name\"];\n const name =\n typeof operation === \"string\" && operations.has(operation)\n ? operation\n : \"ai.operation\";\n const model = name === \"chat\";\n const span = engine.startSpan(name, {\n ...(spanOptions.kind !== undefined ? { kind: spanOptions.kind } : {}),\n ...(spanOptions.startTime !== undefined\n ? { startTime: spanOptions.startTime }\n : {}),\n // An empty SDK context must not replace the engine's local async parent.\n ...(parent && trace.getSpanContext(parent) ? { parent } : {}),\n attributes: {\n \"vercel.span.type\": model\n ? \"model_request\"\n : name === \"execute_tool\"\n ? \"tool\"\n : name === \"agent_step\"\n ? \"step\"\n : \"turn\",\n \"gen_ai.operation.name\": name,\n ...attributes(name, spanOptions.attributes ?? {}),\n },\n });\n let ended = false;\n const wrapped: Span = {\n spanContext: () => span.spanContext(),\n isRecording: () => !closed && !ended && span.isRecording(),\n setAttribute(key, value) {\n return wrapped.setAttributes({ [key]: value });\n },\n setAttributes(values) {\n if (!closed && !ended) {\n span.setAttributes(attributes(name, values));\n if (\n values[\"ai.response.finishReason\"] === \"error\" ||\n (Array.isArray(values[\"gen_ai.response.finish_reasons\"]) &&\n values[\"gen_ai.response.finish_reasons\"].includes(\"error\"))\n ) {\n span.setStatus({ code: SpanStatusCode.ERROR });\n span.setAttribute(\"error.type\", \"provider_error\");\n }\n }\n return wrapped;\n },\n addEvent() {\n // Native events may contain arbitrary exception or provider content.\n return wrapped;\n },\n addLink() {\n return wrapped;\n },\n addLinks() {\n return wrapped;\n },\n setStatus(status) {\n if (!closed && !ended) {\n span.setStatus({ code: status.code });\n if (status.code === SpanStatusCode.ERROR)\n span.setAttribute(\"error.type\", \"provider_error\");\n }\n return wrapped;\n },\n updateName() {\n return wrapped;\n },\n recordException() {\n if (!closed && !ended)\n span.addEvent(\"exception\", { \"exception.type\": \"provider_error\" });\n },\n end(time) {\n if (ended) return;\n ended = true;\n active.delete(wrapped);\n span.end(time);\n },\n };\n if (span.isRecording()) active.set(wrapped, span);\n const current = invocation.getStore();\n const call = current ? calls.get(current.callId) : undefined;\n if (call) {\n if (model) call.model = wrapped;\n if (name === \"execute_tool\" && current?.toolCallId) {\n if (!call.tools.has(current.toolCallId)) toolCount++;\n call.tools.set(current.toolCallId, wrapped);\n }\n }\n return wrapped;\n }\n\n const tracer: Tracer = {\n startSpan,\n startActiveSpan<F extends (span: Span) => unknown>(\n name: string,\n ...args: [F] | [SpanOptions, F] | [SpanOptions, Context, F]\n ): ReturnType<F> {\n const callback = args[args.length - 1] as F;\n const settings = args.length > 1 ? (args[0] as SpanOptions) : {};\n const parent = args.length === 3 ? (args[1] as Context) : undefined;\n const started = startSpan(name, settings, parent);\n return engine.withSpan(started, () => callback(started)) as ReturnType<F>;\n },\n };\n\n const native = options.integration(tracer);\n const wrappedIntegration = new Proxy(native, {\n get(target, property, receiver) {\n const method: unknown = Reflect.get(target, property, receiver);\n if (typeof method !== \"function\") return method;\n return (...args: unknown[]) => {\n let event = args[0] as\n | {\n callId?: string;\n toolCallId?: string;\n toolCall?: { toolCallId?: string };\n execute?: () => PromiseLike<unknown>;\n error?: unknown;\n toolOutput?: { type: string; error?: unknown };\n finishReason?: string;\n usage?: LanguageModelUsage;\n timestamp?: number;\n }\n | undefined;\n const callId = event?.callId;\n if (!event || !callId) return Reflect.apply(method, target, args);\n if (closed) {\n return event?.execute?.();\n }\n if (\n event.error !== undefined ||\n event.toolOutput?.error !== undefined\n ) {\n const failure = new Error(\"Provider operation failed\");\n failure.name = \"ProviderError\";\n event = {\n ...event,\n ...(event.error !== undefined ? { error: failure } : {}),\n ...(event.toolOutput?.error !== undefined\n ? { toolOutput: { ...event.toolOutput, error: failure } }\n : {}),\n };\n args = [event, ...args.slice(1)];\n }\n if (property === \"onStart\") {\n if (calls.size >= engine.maxActiveSpans) return;\n calls.set(callId, { tools: new Map() });\n }\n const call = calls.get(callId);\n if (!call) return event?.execute?.();\n if (\n property === \"onLanguageModelCallEnd\" &&\n call.model &&\n event.usage\n ) {\n // Native OTel ends this span inside the hook; reasoning attributes\n // otherwise depend on its optional supplemental-usage setting.\n call.model.setAttributes({\n \"gen_ai.usage.cache_read.input_tokens\":\n event.usage.inputTokenDetails?.cacheReadTokens,\n \"gen_ai.usage.cache_creation.input_tokens\":\n event.usage.inputTokenDetails?.cacheWriteTokens,\n \"gen_ai.usage.reasoning.output_tokens\":\n event.usage.outputTokenDetails?.reasoningTokens,\n });\n }\n const toolCallId = event.toolCallId ?? event.toolCall?.toolCallId;\n if (\n property === \"onToolExecutionStart\" &&\n toolCount >= engine.maxActiveSpans\n )\n return;\n if (\n property === \"executeLanguageModelCall\" ||\n property === \"executeTool\"\n ) {\n const parent =\n property === \"executeTool\"\n ? toolCallId\n ? call.tools.get(toolCallId)\n : undefined\n : call.model;\n const execute = event.execute;\n const invoke = () =>\n Reflect.apply(method, target, [\n {\n ...event,\n ...(parent && execute\n ? { execute: () => engine.withSpan(parent, execute) }\n : {}),\n },\n ...args.slice(1),\n ]);\n return parent ? engine.withSpan(parent, invoke) : invoke();\n }\n const result = invocation.run(\n { callId, ...(toolCallId ? { toolCallId } : {}) },\n () => Reflect.apply(method, target, args),\n );\n const terminal =\n property === \"onEnd\" ||\n property === \"onAbort\" ||\n property === \"onError\";\n // Error-only streams may end a step without a model-call end event.\n // Finish the existing native span; do not infer usage from its parent.\n if ((property === \"onStepEnd\" || terminal) && call.model) {\n const span = call.model;\n active.get(span)?.setAttribute(\"observability.incomplete\", true);\n if (\n event.finishReason === \"error\" ||\n property === \"onAbort\" ||\n property === \"onError\"\n )\n span.setStatus({ code: SpanStatusCode.ERROR });\n span.end(\n typeof event.timestamp === \"number\"\n ? new Date(event.timestamp)\n : undefined,\n );\n delete call.model;\n }\n if (property === \"onToolExecutionEnd\" && toolCallId) {\n if (call.tools.delete(toolCallId)) toolCount--;\n }\n if (property === \"onLanguageModelCallEnd\") delete call.model;\n if (terminal) {\n for (const span of call.tools.values()) {\n active.get(span)?.setAttribute(\"observability.incomplete\", true);\n span.end();\n }\n toolCount -= call.tools.size;\n calls.delete(callId);\n }\n return result;\n };\n },\n });\n const telemetry: VercelTelemetrySettings = {\n isEnabled: true,\n recordInputs,\n recordOutputs,\n integrations: [wrappedIntegration],\n };\n return {\n telemetry,\n flush: () => engine.flush(),\n getDiagnostics: () => engine.getDiagnostics(),\n shutdown() {\n shutdownPromise ??= (async () => {\n closed = true;\n telemetry.isEnabled = false;\n for (const span of active.values())\n span.setAttribute(\"observability.incomplete\", true);\n if (native.onError) {\n for (const callId of calls.keys()) {\n try {\n Reflect.apply(native.onError, native, [\n { callId, error: new Error(\"Observation ended\") },\n ]);\n } catch {}\n }\n }\n for (const [wrapped, span] of active) {\n span.setAttribute(\"observability.incomplete\", true);\n wrapped.end();\n }\n calls.clear();\n toolCount = 0;\n invocation.disable();\n if (options.shutdownEngine) await engine.shutdown();\n else await engine.flush();\n })();\n return shutdownPromise;\n },\n };\n}\n","import {\n attachVercelAI,\n type VercelAdapterOptions,\n} from \"./adapters/vercel.js\";\nimport {\n createObservability,\n type EventhouseObservabilityOptions,\n} from \"./index.js\";\nimport type { Observability } from \"./engine.js\";\n\nexport type {\n VercelAdapterOptions,\n VercelTelemetrySettings,\n} from \"./adapters/vercel.js\";\n\nexport type VercelObservabilityOptions = VercelAdapterOptions &\n (\n | (EventhouseObservabilityOptions & { engine?: never })\n | { engine: Observability }\n );\n\nexport function observeVercelAI(options: VercelObservabilityOptions) {\n if (typeof options?.integration !== \"function\")\n throw new Error(\n \"A native Vercel telemetry integration factory is required\",\n );\n if (options.engine) return attachVercelAI(options.engine, options);\n if (options.shutdownEngine === false)\n throw new Error(\n \"shutdownEngine: false requires an externally owned engine\",\n );\n const engine = createObservability(options);\n try {\n return attachVercelAI(engine, {\n ...options,\n shutdownEngine: true,\n });\n } catch (error) {\n void engine.shutdown().catch(() => {});\n throw error;\n }\n}\n"],"mappings":"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","names":[]}
@@ -1,14 +1,21 @@
1
1
  # Architecture
2
2
 
3
3
  ```text
4
- Existing Copilot session -> independent event adapter -> private OTel TracerProvider
4
+ Copilot events / OpenAI requests / Vercel native telemetry
5
+ -> independent SDK adapters -> private OTel TracerProvider
5
6
  -> privacy filtering -> OpenTelemetry BatchSpanProcessor -> SpanExporter
6
7
  -> Event Hubs producer / Fabric Eventstream Custom endpoint SOURCE
7
8
  -> Eventhouse / KQL / native Real-Time Dashboards
8
- -> optional existing OTLP/HTTP destination
9
+ -> application-supplied standard exporter (for example, official OTLP/HTTP)
9
10
  ```
10
11
 
11
- There is one root npm package, not a package-per-module workspace. `src/engine.ts` configures the official OpenTelemetry SDK. `src/adapters/copilot.ts` consumes its small `Observability` interface. `src/exporters/eventstream.ts` implements the standard OTel `SpanExporter` contract; the OTLP path uses the official `OTLPTraceExporter` directly. `src/index.ts` and `src/copilot.ts` are the public entry points.
12
+ There is one root npm package, not a package-per-module workspace. `src/engine.ts` configures the official OpenTelemetry SDK. SDK adapters consume its small `Observability` interface. `src/exporters/eventstream.ts` implements the standard OTel `SpanExporter` contract. Fabric is the only built-in transport; other transports are supplied through `exporter`, with their dependencies and settings owned by the application. The root runtime does not load an OTLP exporter. The root exports `createObservability`, `EventstreamExporter`, and public configuration, observability, exporter, diagnostic, and redactor types. `createEngine`, `serializeSpan`, and `redactSecrets` remain internal module exports for implementation and tests, not supported root imports. `/copilot`, `/openai`, and `/vercel` provide independent integrations. SDK dependencies are optional peers and are not loaded by the root runtime.
13
+
14
+ Instrument each model call through one adapter. If Vercel already records a
15
+ provider call, also wrapping that provider's OpenAI client would produce another
16
+ model span and double-count token usage in the supplied analytics. Applications
17
+ can share an engine across independent adapters, with one explicit owner of its
18
+ shutdown lifecycle.
12
19
 
13
20
  ## OpenTelemetry coexistence
14
21
 
@@ -16,6 +23,15 @@ The engine creates a **private OpenTelemetry 3 TracerProvider**. It never regist
16
23
 
17
24
  This design intentionally does not attach a processor to an existing provider: current providers do not offer safe mutable processor registration. Existing application spans continue to use their existing exporters. To export other spans here, configure a processor/transport at your application's own provider initialization; the SDK does not steal or duplicate that provider's lifecycle.
18
25
 
26
+ The public `EventstreamExporter` supports this application-owned path: construct
27
+ the official SDK 3 `BatchSpanProcessor({ exporter })` and pass the processor in
28
+ `new TracerProvider({ spanProcessors: [...] })`. See the
29
+ [provider example](extending.md#export-spans-from-an-application-owned-provider).
30
+ The application owns that provider's resource, sampling, privacy policy, global
31
+ registration, and shutdown. Direct exporter use does not apply the private
32
+ engine's sanitizer or diagnostics. Use separate exporter instances for separate
33
+ provider lifecycles.
34
+
19
35
  ## Reliability and lifecycle
20
36
 
21
37
  Event callbacks create/end spans without waiting on I/O. The standard `BatchSpanProcessor` owns batching, its bounded queue, scheduling, and flush lifecycle. A thin privacy/diagnostic wrapper defers actual exporter calls to a microtask so network work does not run inside event callbacks. `maxConcurrentExports` (default `4`) limits underlying exporter calls, including calls whose callback deadline has expired. Native batches wait for a concurrency slot within their callback deadline and the existing `maxQueueSize` admission budget; there is no second span queue. This bounds native `forceFlush` fan-out without dropping a healthy admitted backlog. Waiting spans still count toward queue depth, so new admissions are dropped if the budget fills. Unfinished spans have a separate configurable bound. Per-span attribute/event/link counts and value sizes are limited.
@@ -7,7 +7,7 @@ import { observeCopilot } from "@saguiitay/eventhouse-ai-observability/copilot";
7
7
 
8
8
  const observer = observeCopilot(session, {
9
9
  serviceName: "my-agent",
10
- // Configure Fabric or an OTLP destination using options/environment.
10
+ // Configure Fabric using options/environment, or supply a custom exporter.
11
11
  captureContent: false,
12
12
  });
13
13
  try {
package/docs/extending.md CHANGED
@@ -30,4 +30,48 @@ Subscribe without changing framework behavior, use exact framework timestamps, a
30
30
 
31
31
  Supply a standard OpenTelemetry `SpanExporter` via `createObservability({ exporter })`; it receives sanitized `ReadableSpan[]` values and reports success/failure through the standard result callback. Batching and flush scheduling belong to OTel's `BatchSpanProcessor`. Use the callback exactly once, implement `shutdown`, bound I/O/retries, and never log raw errors with credentials.
32
32
 
33
- Vercel AI SDK, OpenAI Agents SDK, LangChain/LangGraph, and Semantic Kernel adapters are intentionally not implemented.
33
+ ## Export spans from an application-owned provider
34
+
35
+ If your application already initializes an OpenTelemetry provider, pass the
36
+ public `EventstreamExporter` to the official SDK 3 `BatchSpanProcessor` when
37
+ constructing that provider:
38
+
39
+ ```typescript
40
+ import { EventstreamExporter } from "@saguiitay/eventhouse-ai-observability";
41
+ import { BatchSpanProcessor, TracerProvider } from "@opentelemetry/sdk-trace";
42
+ import { resourceFromAttributes } from "@opentelemetry/resources";
43
+
44
+ const exporter = new EventstreamExporter({
45
+ connectionString: process.env.FABRIC_EVENTSTREAM_CONNECTION_STRING!,
46
+ // entityName: "my-eventstream", // Required if the string lacks EntityPath.
47
+ });
48
+ const provider = new TracerProvider({
49
+ resource: resourceFromAttributes({ "service.name": "my-application" }),
50
+ spanProcessors: [new BatchSpanProcessor({ exporter })],
51
+ });
52
+
53
+ provider.getTracer("my-application").startSpan("application-work").end();
54
+ await provider.shutdown();
55
+ ```
56
+
57
+ SDK 3 takes an options object (`{ exporter }`) in `BatchSpanProcessor` and
58
+ processors in the provider constructor; do not call
59
+ `addSpanProcessor`. Add this processor alongside your application's other
60
+ processors at initialization. Keep any global provider, context-manager, and
61
+ propagator setup under application control. The application owns flushing and
62
+ shutdown; do not share this exporter instance with a separate observability
63
+ engine.
64
+
65
+ Direct export bypasses the engine's privacy filtering, redactor, diagnostics,
66
+ and resource/sampling configuration. Apply your application's privacy policy
67
+ before spans reach the exporter. `EventstreamExporter` serializes spans and
68
+ handles transport limits; it is not a sanitizer.
69
+
70
+ The root API provides `createObservability`, `EventstreamExporter`, and public
71
+ configuration, exporter, observability, diagnostic, and redactor types.
72
+ `createEngine`, `serializeSpan`, and `redactSecrets` are internal helpers, not
73
+ public imports. Use `createObservability({ exporter })` for custom transports
74
+ and the `redact` option for application-specific attribute filtering. The
75
+ adapter subpaths remain `/copilot`, `/openai`, and `/vercel`.
76
+
77
+ The existing [Copilot](copilot-adapter.md), [OpenAI](openai-adapter.md), and [Vercel AI](vercel-adapter.md) integrations use different instrumentation surfaces while sharing the engine and transports. Add framework-specific entry points rather than introducing separate copies of batching, privacy, or Fabric export.
@@ -1,5 +1,29 @@
1
1
  # Fabric setup
2
2
 
3
+ ## Obtain the setup assets
4
+
5
+ Starting with the prepared **0.2.0** package, install the SDK first and extract
6
+ `fabric-setup.zip` from its installed package root. See the
7
+ [PowerShell install/extract commands](../README.md#fabric-setup-bundle-and-ingestion-check).
8
+ The source repository is private: GitHub Releases and Actions artifacts are
9
+ collaborator-only alternatives, not required public downloads.
10
+
11
+ **0.2.0 is not yet published.** npmjs 0.1.2 and GitHub Packages 0.1.1 are
12
+ published and verified but do not include the ZIP. Use the manual setup below
13
+ with those versions, or install a locally packed 0.2.0 tarball for evaluation.
14
+
15
+ The extracted root contains `README.md`, `manifest.json`, and `fabric\`.
16
+ Follow its README and preserve that structure. The manifest records the matching
17
+ package version, schema version, and SHA-256 file hashes. It contains only this
18
+ personal project's authored generic public-API setup definitions and synthetic
19
+ samples, not Microsoft service internals, confidential source/configuration,
20
+ tenant data, or credentials. Fresh generation does not reuse local bindings;
21
+ source provenance must also be reviewed.
22
+
23
+ Repository collaborators can still run `pnpm bundle:fabric` in the source checkout.
24
+ The commands below use paths relative to the extracted bundle or checkout.
25
+ Generating and packaging assets is offline; deployment requires your credentials.
26
+
3
27
  ## Prerequisites
4
28
 
5
29
  - Fabric-capacity or trial workspace; Contributor or higher for creation.