@cuylabs/agent-a365-observability 4.5.0 → 4.6.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/dist/index.js CHANGED
@@ -1,3 +1,10 @@
1
+ // src/types.ts
2
+ var A365_EXPORTER_EVENT_NAMES = {
3
+ export: "agent365-export",
4
+ exportGroup: "export-group",
5
+ exportPartitionSpanMissingIdentity: "export-partition-span-missing-identity"
6
+ };
7
+
1
8
  // src/errors.ts
2
9
  var A365ObservabilityModuleLoadError = class extends Error {
3
10
  constructor(message, options) {
@@ -5,41 +12,14 @@ var A365ObservabilityModuleLoadError = class extends Error {
5
12
  this.name = "A365ObservabilityModuleLoadError";
6
13
  }
7
14
  };
8
-
9
- // src/baggage-keys.ts
10
- var A365_BAGGAGE_KEYS = {
11
- tenantId: "microsoft.tenant.id",
12
- agentId: "gen_ai.agent.id",
13
- agentName: "gen_ai.agent.name",
14
- agentDescription: "gen_ai.agent.description",
15
- agentVersion: "gen_ai.agent.version",
16
- agentPlatformId: "microsoft.a365.agent.platform.id",
17
- agentAuid: "microsoft.agent.user.id",
18
- agentEmail: "microsoft.agent.user.email",
19
- agentBlueprintId: "microsoft.a365.agent.blueprint.id",
20
- sessionId: "microsoft.session.id",
21
- sessionDescription: "microsoft.session.description",
22
- conversationId: "gen_ai.conversation.id",
23
- conversationItemLink: "microsoft.conversation.item.link",
24
- channelName: "microsoft.channel.name",
25
- channelLink: "microsoft.channel.link",
26
- userId: "user.id",
27
- userName: "user.name",
28
- userEmail: "user.email",
29
- callerClientIp: "client.address",
30
- callerAgentId: "microsoft.a365.caller.agent.id",
31
- callerAgentName: "microsoft.a365.caller.agent.name",
32
- callerAgentAuid: "microsoft.a365.caller.agent.user.id",
33
- callerAgentEmail: "microsoft.a365.caller.agent.user.email",
34
- callerAgentBlueprintId: "microsoft.a365.caller.agent.blueprint.id",
35
- callerAgentPlatformId: "microsoft.a365.caller.agent.platform.id",
36
- callerAgentVersion: "microsoft.a365.caller.agent.version",
37
- serviceName: "service.name",
38
- serverAddress: "server.address",
39
- serverPort: "server.port"
15
+ var A365ObservabilityHostingModuleLoadError = class extends Error {
16
+ constructor(message, options) {
17
+ super(message, options);
18
+ this.name = "A365ObservabilityHostingModuleLoadError";
19
+ }
40
20
  };
41
21
 
42
- // src/internal/parsing.ts
22
+ // src/internal/values.ts
43
23
  function setPair(target, key, value) {
44
24
  if (value === null || value === void 0) {
45
25
  return;
@@ -115,7 +95,480 @@ function isRecord(value) {
115
95
  return typeof value === "object" && value !== null;
116
96
  }
117
97
 
118
- // src/baggage.ts
98
+ // src/auth/oauth-token-endpoint.ts
99
+ var CLIENT_ASSERTION_TYPE = "urn:ietf:params:oauth:client-assertion-type:jwt-bearer";
100
+ var DEFAULT_TOKEN_LIFETIME_MS = 55 * 60 * 1e3;
101
+ async function requestClientSecretToken(client, request, extra) {
102
+ const body = new URLSearchParams({
103
+ grant_type: "client_credentials",
104
+ client_id: request.clientId,
105
+ client_secret: request.clientSecret,
106
+ scope: request.scope
107
+ });
108
+ if (extra?.fmiPath) {
109
+ body.set("fmi_path", extra.fmiPath);
110
+ }
111
+ return await requestToken(client, body);
112
+ }
113
+ async function requestClientAssertionToken(client, request, extra) {
114
+ const body = new URLSearchParams({
115
+ grant_type: "client_credentials",
116
+ client_id: request.clientId,
117
+ client_assertion: request.clientAssertion,
118
+ client_assertion_type: CLIENT_ASSERTION_TYPE,
119
+ scope: request.scope
120
+ });
121
+ if (extra?.fmiPath) {
122
+ body.set("fmi_path", extra.fmiPath);
123
+ }
124
+ return await requestToken(client, body);
125
+ }
126
+ async function requestToken(client, body) {
127
+ const response = await client.fetch(
128
+ `https://login.microsoftonline.com/${encodeURIComponent(
129
+ client.tenantId
130
+ )}/oauth2/v2.0/token`,
131
+ {
132
+ method: "POST",
133
+ headers: { "Content-Type": "application/x-www-form-urlencoded" },
134
+ body: body.toString()
135
+ }
136
+ );
137
+ const payload = parseTokenEndpointResponse(await response.text());
138
+ if (!response.ok || !payload.access_token) {
139
+ throw new Error(
140
+ payload.error_description || payload.error || `token endpoint returned HTTP ${response.status}`
141
+ );
142
+ }
143
+ const expiresInMs = typeof payload.expires_in === "number" && payload.expires_in > 0 ? payload.expires_in * 1e3 : DEFAULT_TOKEN_LIFETIME_MS;
144
+ return {
145
+ accessToken: payload.access_token,
146
+ expiresAtMs: Date.now() + expiresInMs
147
+ };
148
+ }
149
+ function parseTokenEndpointResponse(body) {
150
+ if (!body) {
151
+ return {};
152
+ }
153
+ try {
154
+ return JSON.parse(body);
155
+ } catch {
156
+ return { error_description: body };
157
+ }
158
+ }
159
+
160
+ // src/auth/s2s-token-resolver.ts
161
+ var DEFAULT_FMI_SCOPE = "api://AzureADTokenExchange/.default";
162
+ var DEFAULT_OBSERVABILITY_SCOPE = "api://9b975845-388f-4429-889e-eab1ef63949c/.default";
163
+ var DEFAULT_REFRESH_SKEW_MS = 5 * 60 * 1e3;
164
+ function createA365S2STokenResolver(options) {
165
+ const normalized = normalizeOptions(options);
166
+ const cache = /* @__PURE__ */ new Map();
167
+ return async (agentId, tenantId) => {
168
+ const request = {
169
+ ...normalized,
170
+ agentId: firstNonEmpty(agentId, normalized.agentId) ?? "",
171
+ tenantId: firstNonEmpty(tenantId, normalized.tenantId) ?? ""
172
+ };
173
+ const cacheKey = `${request.tenantId}:${request.agentId}`;
174
+ const cached = cache.get(cacheKey);
175
+ if (cached && cached.expiresAtMs - request.refreshSkewMs > Date.now()) {
176
+ return cached.accessToken;
177
+ }
178
+ try {
179
+ const token = await acquireObservabilityToken(request);
180
+ cache.set(cacheKey, token);
181
+ return token.accessToken;
182
+ } catch (error) {
183
+ request.logger?.warn("Failed to acquire Agent 365 S2S token", {
184
+ error: error instanceof Error ? error.message : String(error),
185
+ useManagedIdentity: request.useManagedIdentity
186
+ });
187
+ return null;
188
+ }
189
+ };
190
+ }
191
+ function normalizeOptions(options) {
192
+ return {
193
+ tenantId: requireNonEmpty("tenantId", options.tenantId),
194
+ agentId: requireNonEmpty("agentId", options.agentId),
195
+ blueprintClientId: requireNonEmpty(
196
+ "blueprintClientId",
197
+ options.blueprintClientId
198
+ ),
199
+ ...options.blueprintClientSecret ? { blueprintClientSecret: options.blueprintClientSecret } : {},
200
+ useManagedIdentity: options.useManagedIdentity ?? false,
201
+ ...options.managedIdentityAssertionProvider ? {
202
+ managedIdentityAssertionProvider: options.managedIdentityAssertionProvider
203
+ } : {},
204
+ allowManagedIdentityClientSecretFallback: options.allowManagedIdentityClientSecretFallback ?? true,
205
+ observabilityScope: options.observabilityScope ?? DEFAULT_OBSERVABILITY_SCOPE,
206
+ fmiScope: options.fmiScope ?? DEFAULT_FMI_SCOPE,
207
+ refreshSkewMs: options.refreshSkewMs ?? DEFAULT_REFRESH_SKEW_MS,
208
+ ...options.logger ? { logger: options.logger } : {},
209
+ fetch: options.fetch ?? globalThis.fetch.bind(globalThis)
210
+ };
211
+ }
212
+ async function acquireObservabilityToken(options) {
213
+ const t1Token = await acquireFmiToken(options);
214
+ return await requestClientAssertionToken(options, {
215
+ clientId: options.agentId,
216
+ clientAssertion: t1Token.accessToken,
217
+ scope: options.observabilityScope
218
+ });
219
+ }
220
+ async function acquireFmiToken(options) {
221
+ if (options.useManagedIdentity) {
222
+ try {
223
+ const assertion = await options.managedIdentityAssertionProvider?.();
224
+ if (!assertion) {
225
+ throw new Error(
226
+ "managed identity assertion provider returned no token"
227
+ );
228
+ }
229
+ return await requestClientAssertionToken(
230
+ options,
231
+ {
232
+ clientId: options.blueprintClientId,
233
+ clientAssertion: assertion,
234
+ scope: options.fmiScope
235
+ },
236
+ { fmiPath: options.agentId }
237
+ );
238
+ } catch (error) {
239
+ if (!options.allowManagedIdentityClientSecretFallback || !options.blueprintClientSecret) {
240
+ throw error;
241
+ }
242
+ options.logger?.warn(
243
+ "Managed identity FMI token exchange failed; falling back to blueprint client secret",
244
+ { error: error instanceof Error ? error.message : String(error) }
245
+ );
246
+ }
247
+ }
248
+ return await requestClientSecretToken(
249
+ options,
250
+ {
251
+ clientId: options.blueprintClientId,
252
+ clientSecret: requireNonEmpty(
253
+ "blueprintClientSecret",
254
+ options.blueprintClientSecret
255
+ ),
256
+ scope: options.fmiScope
257
+ },
258
+ { fmiPath: options.agentId }
259
+ );
260
+ }
261
+ function requireNonEmpty(name, value) {
262
+ const normalized = value?.trim();
263
+ if (!normalized) {
264
+ throw new Error(`${name} is required`);
265
+ }
266
+ return normalized;
267
+ }
268
+
269
+ // src/auth/env.ts
270
+ var TRUE_VALUES = /* @__PURE__ */ new Set(["1", "true", "yes", "y", "on"]);
271
+ var FALSE_VALUES = /* @__PURE__ */ new Set(["0", "false", "no", "n", "off"]);
272
+ function resolveA365ObservabilityEnvironment(env = process.env) {
273
+ const reader = createEnvReader(env);
274
+ return {
275
+ tenantId: reader(
276
+ "A365_OBSERVABILITY_TENANT_ID",
277
+ "agent365Observability__tenantId"
278
+ ),
279
+ agentId: reader(
280
+ "A365_OBSERVABILITY_AGENT_ID",
281
+ "agent365Observability__agentId"
282
+ ),
283
+ agentName: reader(
284
+ "A365_OBSERVABILITY_AGENT_NAME",
285
+ "agent365Observability__agentName"
286
+ ),
287
+ agentDescription: reader(
288
+ "A365_OBSERVABILITY_AGENT_DESCRIPTION",
289
+ "agent365Observability__agentDescription"
290
+ ),
291
+ blueprintClientId: reader(
292
+ "A365_OBSERVABILITY_BLUEPRINT_CLIENT_ID",
293
+ "A365_OBSERVABILITY_CLIENT_ID",
294
+ "agent365Observability__clientId",
295
+ "agent365Observability__agentBlueprintId"
296
+ ),
297
+ blueprintClientSecret: reader(
298
+ "A365_OBSERVABILITY_BLUEPRINT_CLIENT_SECRET",
299
+ "A365_OBSERVABILITY_CLIENT_SECRET",
300
+ "agent365Observability__clientSecret"
301
+ ),
302
+ useManagedIdentity: readBoolean(
303
+ reader(
304
+ "A365_OBSERVABILITY_USE_MANAGED_IDENTITY",
305
+ "agent365Observability__useManagedIdentity"
306
+ )
307
+ ),
308
+ observabilityScope: reader(
309
+ "A365_OBSERVABILITY_SCOPE",
310
+ "A365_OBSERVABILITY_SCOPES",
311
+ "agent365Observability__observabilityScope"
312
+ ),
313
+ fmiScope: reader(
314
+ "A365_OBSERVABILITY_FMI_SCOPE",
315
+ "A365_FMI_SCOPE",
316
+ "agent365Observability__fmiScope"
317
+ ),
318
+ configuration: {
319
+ exporterEnabled: readBoolean(
320
+ reader(
321
+ "ENABLE_A365_OBSERVABILITY_EXPORTER",
322
+ "agent365Observability__exporterEnabled"
323
+ )
324
+ ),
325
+ domainOverride: reader(
326
+ "A365_OBSERVABILITY_DOMAIN_OVERRIDE",
327
+ "agent365Observability__domainOverride"
328
+ ),
329
+ logLevel: reader(
330
+ "A365_OBSERVABILITY_LOG_LEVEL",
331
+ "agent365Observability__logLevel"
332
+ ),
333
+ authenticationScopes: splitScopes(
334
+ reader(
335
+ "A365_OBSERVABILITY_AUTHENTICATION_SCOPES",
336
+ "A365_OBSERVABILITY_SCOPES_OVERRIDE",
337
+ "agent365Observability__authenticationScopes"
338
+ )
339
+ ),
340
+ perRequest: {
341
+ enabled: readBoolean(
342
+ reader(
343
+ "ENABLE_A365_OBSERVABILITY_PER_REQUEST_EXPORT",
344
+ "agent365Observability__perRequestExportEnabled"
345
+ )
346
+ ),
347
+ maxTraces: readNumber(
348
+ reader(
349
+ "A365_PER_REQUEST_MAX_TRACES",
350
+ "agent365Observability__perRequestMaxTraces"
351
+ )
352
+ ),
353
+ maxSpansPerTrace: readNumber(
354
+ reader(
355
+ "A365_PER_REQUEST_MAX_SPANS_PER_TRACE",
356
+ "agent365Observability__perRequestMaxSpansPerTrace"
357
+ )
358
+ ),
359
+ maxConcurrentExports: readNumber(
360
+ reader(
361
+ "A365_PER_REQUEST_MAX_CONCURRENT_EXPORTS",
362
+ "agent365Observability__perRequestMaxConcurrentExports"
363
+ )
364
+ ),
365
+ flushGraceMs: readNumber(
366
+ reader(
367
+ "A365_PER_REQUEST_FLUSH_GRACE_MS",
368
+ "agent365Observability__perRequestFlushGraceMs"
369
+ )
370
+ ),
371
+ maxTraceAgeMs: readNumber(
372
+ reader(
373
+ "A365_PER_REQUEST_MAX_TRACE_AGE_MS",
374
+ "agent365Observability__perRequestMaxTraceAgeMs"
375
+ )
376
+ )
377
+ }
378
+ }
379
+ };
380
+ }
381
+ function createA365S2STokenResolverFromEnv(options = {}) {
382
+ const env = resolveA365ObservabilityEnvironment(options.env);
383
+ return createA365S2STokenResolver({
384
+ ...options,
385
+ tenantId: requireValue(
386
+ "tenantId",
387
+ firstNonEmpty(options.tenantId, env.tenantId)
388
+ ),
389
+ agentId: requireValue(
390
+ "agentId",
391
+ firstNonEmpty(options.agentId, env.agentId)
392
+ ),
393
+ blueprintClientId: requireValue(
394
+ "blueprintClientId",
395
+ firstNonEmpty(options.blueprintClientId, env.blueprintClientId)
396
+ ),
397
+ blueprintClientSecret: firstNonEmpty(
398
+ options.blueprintClientSecret,
399
+ env.blueprintClientSecret
400
+ ),
401
+ useManagedIdentity: options.useManagedIdentity ?? env.useManagedIdentity ?? Boolean(options.managedIdentityAssertionProvider),
402
+ observabilityScope: firstNonEmpty(
403
+ options.observabilityScope,
404
+ env.observabilityScope
405
+ ),
406
+ fmiScope: firstNonEmpty(options.fmiScope, env.fmiScope)
407
+ });
408
+ }
409
+ function createEnvReader(env) {
410
+ const normalized = /* @__PURE__ */ new Map();
411
+ for (const [key, value] of Object.entries(env)) {
412
+ if (value !== void 0) {
413
+ normalized.set(key.toLowerCase(), value);
414
+ }
415
+ }
416
+ return (...names) => {
417
+ for (const name of names) {
418
+ const value = firstNonEmpty(
419
+ env[name],
420
+ normalized.get(name.toLowerCase())
421
+ );
422
+ if (value) {
423
+ return value;
424
+ }
425
+ }
426
+ return void 0;
427
+ };
428
+ }
429
+ function readBoolean(value) {
430
+ const normalized = value?.trim().toLowerCase();
431
+ if (!normalized) {
432
+ return void 0;
433
+ }
434
+ if (TRUE_VALUES.has(normalized)) {
435
+ return true;
436
+ }
437
+ if (FALSE_VALUES.has(normalized)) {
438
+ return false;
439
+ }
440
+ return void 0;
441
+ }
442
+ function readNumber(value) {
443
+ const normalized = value?.trim();
444
+ if (!normalized) {
445
+ return void 0;
446
+ }
447
+ const parsed = Number(normalized);
448
+ return Number.isFinite(parsed) ? parsed : void 0;
449
+ }
450
+ function splitScopes(value) {
451
+ const scopes = value?.split(/\s+/).filter(Boolean);
452
+ return scopes && scopes.length > 0 ? scopes : void 0;
453
+ }
454
+ function requireValue(name, value) {
455
+ if (!value) {
456
+ throw new Error(`${name} is required`);
457
+ }
458
+ return value;
459
+ }
460
+
461
+ // src/runtime/module-loader.ts
462
+ var OBSERVABILITY_PACKAGE = "@microsoft/agents-a365-observability";
463
+ var OBSERVABILITY_HOSTING_PACKAGE = "@microsoft/agents-a365-observability-hosting";
464
+ async function loadObservabilityModule(options = {}) {
465
+ try {
466
+ if (options.getObservabilityModule) {
467
+ return await options.getObservabilityModule();
468
+ }
469
+ return await import(OBSERVABILITY_PACKAGE);
470
+ } catch (error) {
471
+ throw new A365ObservabilityModuleLoadError(
472
+ `Unable to load ${OBSERVABILITY_PACKAGE}. Install @microsoft/agents-a365-observability and @microsoft/agents-a365-runtime before using @cuylabs/agent-a365-observability.`,
473
+ { cause: error }
474
+ );
475
+ }
476
+ }
477
+ async function loadObservabilityHostingModule(options = {}) {
478
+ try {
479
+ if (options.getObservabilityHostingModule) {
480
+ return await options.getObservabilityHostingModule();
481
+ }
482
+ return await import(OBSERVABILITY_HOSTING_PACKAGE);
483
+ } catch (error) {
484
+ throw new A365ObservabilityHostingModuleLoadError(
485
+ `Unable to load ${OBSERVABILITY_HOSTING_PACKAGE}. Install @microsoft/agents-a365-observability-hosting before using OBO Agent 365 observability helpers.`,
486
+ { cause: error }
487
+ );
488
+ }
489
+ }
490
+
491
+ // src/auth/obo-token-cache.ts
492
+ function createA365OboTokenResolver(options = {}) {
493
+ let cachePromise;
494
+ return async (agentId, tenantId) => {
495
+ cachePromise ??= getAgenticTokenCache(options);
496
+ const cache = await cachePromise;
497
+ return cache.getObservabilityToken(agentId, tenantId);
498
+ };
499
+ }
500
+ async function refreshA365OboObservabilityToken(options) {
501
+ const cache = await getAgenticTokenCache(options);
502
+ await cache.RefreshObservabilityToken(
503
+ options.agentId,
504
+ options.tenantId,
505
+ options.turnContext,
506
+ options.authorization,
507
+ options.scopes ?? [],
508
+ options.authHandlerName
509
+ );
510
+ }
511
+ async function invalidateA365OboObservabilityToken(agentId, tenantId, options = {}) {
512
+ const cache = await getAgenticTokenCache(options);
513
+ cache.invalidateToken?.(agentId, tenantId);
514
+ }
515
+ async function invalidateAllA365OboObservabilityTokens(options = {}) {
516
+ const cache = await getAgenticTokenCache(options);
517
+ cache.invalidateAll?.();
518
+ }
519
+ async function getAgenticTokenCache(options) {
520
+ if (options.cache) {
521
+ return options.cache;
522
+ }
523
+ const module = await loadObservabilityHostingModule(options);
524
+ return getCacheFromModule(module);
525
+ }
526
+ function getCacheFromModule(module) {
527
+ if (module.AgenticTokenCacheInstance) {
528
+ return module.AgenticTokenCacheInstance;
529
+ }
530
+ if (module.AgenticTokenCache) {
531
+ return new module.AgenticTokenCache();
532
+ }
533
+ throw new Error(
534
+ "Agent 365 observability hosting module does not export AgenticTokenCache."
535
+ );
536
+ }
537
+
538
+ // src/context/baggage-keys.ts
539
+ var A365_BAGGAGE_KEYS = {
540
+ tenantId: "microsoft.tenant.id",
541
+ agentId: "gen_ai.agent.id",
542
+ agentName: "gen_ai.agent.name",
543
+ agentDescription: "gen_ai.agent.description",
544
+ agentVersion: "gen_ai.agent.version",
545
+ agentPlatformId: "microsoft.a365.agent.platform.id",
546
+ agentAuid: "microsoft.agent.user.id",
547
+ agentEmail: "microsoft.agent.user.email",
548
+ agentBlueprintId: "microsoft.a365.agent.blueprint.id",
549
+ sessionId: "microsoft.session.id",
550
+ sessionDescription: "microsoft.session.description",
551
+ conversationId: "gen_ai.conversation.id",
552
+ conversationItemLink: "microsoft.conversation.item.link",
553
+ channelName: "microsoft.channel.name",
554
+ channelLink: "microsoft.channel.link",
555
+ userId: "user.id",
556
+ userName: "user.name",
557
+ userEmail: "user.email",
558
+ callerClientIp: "client.address",
559
+ callerAgentId: "microsoft.a365.caller.agent.id",
560
+ callerAgentName: "microsoft.a365.caller.agent.name",
561
+ callerAgentAuid: "microsoft.a365.caller.agent.user.id",
562
+ callerAgentEmail: "microsoft.a365.caller.agent.user.email",
563
+ callerAgentBlueprintId: "microsoft.a365.caller.agent.blueprint.id",
564
+ callerAgentPlatformId: "microsoft.a365.caller.agent.platform.id",
565
+ callerAgentVersion: "microsoft.a365.caller.agent.version",
566
+ serviceName: "service.name",
567
+ serverAddress: "server.address",
568
+ serverPort: "server.port"
569
+ };
570
+
571
+ // src/context/baggage.ts
119
572
  function buildA365BaggagePairs(context) {
120
573
  const pairs = {};
121
574
  for (const [key, value] of Object.entries(context.extraBaggage ?? {})) {
@@ -173,28 +626,7 @@ function buildA365BaggagePairs(context) {
173
626
  return pairs;
174
627
  }
175
628
 
176
- // src/tracing-config.ts
177
- function createA365TracingConfig(options = {}) {
178
- const spanAttributes = {
179
- ...options.spanAttributes ?? {}
180
- };
181
- if (isNonEmpty(options.tenantId)) {
182
- spanAttributes[A365_BAGGAGE_KEYS.tenantId] = options.tenantId.trim();
183
- }
184
- return {
185
- ...isNonEmpty(options.agentId) ? { agentId: options.agentId.trim() } : {},
186
- ...isNonEmpty(options.agentDescription) ? { agentDescription: options.agentDescription.trim() } : {},
187
- ...isNonEmpty(options.agentVersion) ? { agentVersion: options.agentVersion.trim() } : {},
188
- ...options.useGenAIOpenTelemetry !== void 0 ? { useGenAIOpenTelemetry: options.useGenAIOpenTelemetry } : {},
189
- ...options.telemetryIntegrations ? { telemetryIntegrations: options.telemetryIntegrations } : {},
190
- ...options.useGlobalTelemetryIntegrations !== void 0 ? {
191
- useGlobalTelemetryIntegrations: options.useGlobalTelemetryIntegrations
192
- } : {},
193
- ...Object.keys(spanAttributes).length > 0 ? { spanAttributes } : {}
194
- };
195
- }
196
-
197
- // src/turn-context.ts
629
+ // src/context/turn-context.ts
198
630
  function createA365ContextFromTurnContext(turnContext, options = {}) {
199
631
  const { serviceUrl, useRecipientIdAsAgentId, ...requestOptions } = options;
200
632
  const activity = turnContext.activity ?? {};
@@ -215,132 +647,144 @@ function createA365ContextFromTurnContext(turnContext, options = {}) {
215
647
  readPath(channelData, ["tenant", "id"]),
216
648
  readPath(channelData, ["tenantId"])
217
649
  );
218
- return {
219
- ...requestOptions,
220
- ...tenantId ? { tenantId } : {},
221
- ...agentId ? { agentId } : {},
222
- ...firstNonEmpty(requestOptions.agentName, activity.recipient?.name) ? {
223
- agentName: firstNonEmpty(
224
- requestOptions.agentName,
225
- activity.recipient?.name
226
- )
227
- } : {},
228
- // Mirrors Microsoft's A365 observability-hosting helper, which maps
229
- // recipient.role to gen_ai.agent.description.
230
- ...firstNonEmpty(requestOptions.agentDescription, activity.recipient?.role) ? {
231
- agentDescription: firstNonEmpty(
232
- requestOptions.agentDescription,
233
- activity.recipient?.role
234
- )
235
- } : {},
236
- ...firstNonEmpty(requestOptions.agentAuid, activity.recipient?.aadObjectId) ? {
237
- agentAuid: firstNonEmpty(
238
- requestOptions.agentAuid,
239
- activity.recipient?.aadObjectId
240
- )
241
- } : {},
242
- ...firstNonEmpty(
650
+ const agentName = firstNonEmpty(
651
+ requestOptions.agentName,
652
+ activity.recipient?.name
653
+ );
654
+ const agentDescription = firstNonEmpty(
655
+ requestOptions.agentDescription,
656
+ activity.recipient?.role
657
+ );
658
+ const conversationId = firstNonEmpty(
659
+ requestOptions.conversationId,
660
+ activity.conversation?.id
661
+ );
662
+ const conversationItemLink = firstNonEmpty(
663
+ requestOptions.conversationItemLink,
664
+ activity.serviceUrl
665
+ );
666
+ const sessionId = firstNonEmpty(
667
+ requestOptions.sessionId,
668
+ requestOptions.conversationId,
669
+ activity.conversation?.id
670
+ );
671
+ const userId = firstNonEmpty(
672
+ requestOptions.userId,
673
+ activity.from?.aadObjectId,
674
+ activity.from?.id,
675
+ readPath(turnContext.identity, ["oid"]),
676
+ readPath(turnContext.identity, ["sub"])
677
+ );
678
+ const userEmail = firstNonEmpty(
679
+ requestOptions.userEmail,
680
+ activity.from?.email,
681
+ activity.from?.userPrincipalName,
682
+ activity.from?.agenticUserId,
683
+ readPath(channelData, ["from", "userPrincipalName"]),
684
+ readPath(turnContext.identity, ["preferred_username"]),
685
+ readPath(turnContext.identity, ["upn"])
686
+ );
687
+ const context = { ...requestOptions };
688
+ setIfDefined(context, "tenantId", tenantId);
689
+ setIfDefined(context, "agentId", agentId);
690
+ setIfDefined(context, "agentName", agentName);
691
+ setIfDefined(context, "agentDescription", agentDescription);
692
+ setIfDefined(
693
+ context,
694
+ "agentAuid",
695
+ firstNonEmpty(requestOptions.agentAuid, activity.recipient?.aadObjectId)
696
+ );
697
+ setIfDefined(
698
+ context,
699
+ "agentBlueprintId",
700
+ firstNonEmpty(
243
701
  requestOptions.agentBlueprintId,
244
702
  activity.recipient?.agenticAppBlueprintId
245
- ) ? {
246
- agentBlueprintId: firstNonEmpty(
247
- requestOptions.agentBlueprintId,
248
- activity.recipient?.agenticAppBlueprintId
249
- )
250
- } : {},
251
- ...firstNonEmpty(requestOptions.conversationId, activity.conversation?.id) ? {
252
- conversationId: firstNonEmpty(
253
- requestOptions.conversationId,
254
- activity.conversation?.id
255
- )
256
- } : {},
257
- // Mirrors Microsoft's A365 observability-hosting helper, which maps the
258
- // Activity serviceUrl to microsoft.conversation.item.link.
259
- ...firstNonEmpty(requestOptions.conversationItemLink, activity.serviceUrl) ? {
260
- conversationItemLink: firstNonEmpty(
261
- requestOptions.conversationItemLink,
262
- activity.serviceUrl
263
- )
264
- } : {},
265
- ...firstNonEmpty(
266
- requestOptions.sessionId,
267
- requestOptions.conversationId,
268
- activity.conversation?.id
269
- ) ? {
270
- sessionId: firstNonEmpty(
271
- requestOptions.sessionId,
272
- requestOptions.conversationId,
273
- activity.conversation?.id
274
- )
275
- } : {},
276
- ...firstNonEmpty(requestOptions.channelName, activity.channelId) ? {
277
- channelName: firstNonEmpty(
278
- requestOptions.channelName,
279
- activity.channelId
280
- )
281
- } : {},
282
- ...firstNonEmpty(requestOptions.channelLink, activity.channelIdSubChannel) ? {
283
- channelLink: firstNonEmpty(
284
- requestOptions.channelLink,
285
- activity.channelIdSubChannel
286
- )
287
- } : {},
288
- ...firstNonEmpty(
289
- requestOptions.userId,
290
- activity.from?.aadObjectId,
291
- activity.from?.id,
292
- readPath(turnContext.identity, ["oid"]),
293
- readPath(turnContext.identity, ["sub"])
294
- ) ? {
295
- userId: firstNonEmpty(
296
- requestOptions.userId,
297
- activity.from?.aadObjectId,
298
- activity.from?.id,
299
- readPath(turnContext.identity, ["oid"]),
300
- readPath(turnContext.identity, ["sub"])
301
- )
302
- } : {},
303
- ...firstNonEmpty(requestOptions.userName, activity.from?.name) ? {
304
- userName: firstNonEmpty(requestOptions.userName, activity.from?.name)
305
- } : {},
306
- // Mirrors Microsoft's A365 observability-hosting helper, which treats
307
- // agenticUserId as the caller UPN/email value.
308
- ...firstNonEmpty(
309
- requestOptions.userEmail,
310
- activity.from?.email,
311
- activity.from?.userPrincipalName,
312
- activity.from?.agenticUserId,
313
- readPath(channelData, ["from", "userPrincipalName"]),
314
- readPath(turnContext.identity, ["preferred_username"]),
315
- readPath(turnContext.identity, ["upn"])
316
- ) ? {
317
- userEmail: firstNonEmpty(
318
- requestOptions.userEmail,
319
- activity.from?.email,
320
- activity.from?.userPrincipalName,
321
- activity.from?.agenticUserId,
322
- readPath(channelData, ["from", "userPrincipalName"]),
323
- readPath(turnContext.identity, ["preferred_username"]),
324
- readPath(turnContext.identity, ["upn"])
325
- )
326
- } : {},
327
- ...firstNonEmpty(
703
+ )
704
+ );
705
+ setIfDefined(context, "conversationId", conversationId);
706
+ setIfDefined(context, "conversationItemLink", conversationItemLink);
707
+ setIfDefined(context, "sessionId", sessionId);
708
+ setIfDefined(
709
+ context,
710
+ "channelName",
711
+ firstNonEmpty(requestOptions.channelName, activity.channelId)
712
+ );
713
+ setIfDefined(
714
+ context,
715
+ "channelLink",
716
+ firstNonEmpty(requestOptions.channelLink, activity.channelIdSubChannel)
717
+ );
718
+ setIfDefined(context, "userId", userId);
719
+ setIfDefined(
720
+ context,
721
+ "userName",
722
+ firstNonEmpty(requestOptions.userName, activity.from?.name)
723
+ );
724
+ setIfDefined(context, "userEmail", userEmail);
725
+ setIfDefined(
726
+ context,
727
+ "callerAgentBlueprintId",
728
+ firstNonEmpty(
328
729
  requestOptions.callerAgentBlueprintId,
329
730
  activity.from?.agenticAppBlueprintId
330
- ) ? {
331
- callerAgentBlueprintId: firstNonEmpty(
332
- requestOptions.callerAgentBlueprintId,
333
- activity.from?.agenticAppBlueprintId
334
- )
335
- } : {},
336
- ...requestOptions.serverAddress ? { serverAddress: requestOptions.serverAddress } : endpoint?.serverAddress ? { serverAddress: endpoint.serverAddress } : {},
337
- ...requestOptions.serverPort !== void 0 ? { serverPort: requestOptions.serverPort } : endpoint?.serverPort !== void 0 ? { serverPort: endpoint.serverPort } : {}
338
- };
731
+ )
732
+ );
733
+ setIfDefined(
734
+ context,
735
+ "serverAddress",
736
+ firstNonEmpty(requestOptions.serverAddress, endpoint?.serverAddress)
737
+ );
738
+ setIfDefined(
739
+ context,
740
+ "serverPort",
741
+ requestOptions.serverPort ?? endpoint?.serverPort
742
+ );
743
+ return context;
744
+ }
745
+ function setIfDefined(context, key, value) {
746
+ if (value !== void 0) {
747
+ context[key] = value;
748
+ }
339
749
  }
340
750
 
341
- // src/lifecycle.ts
342
- var OBSERVABILITY_PACKAGE = "@microsoft/agents-a365-observability";
751
+ // src/runtime/per-request-config.ts
752
+ var PER_REQUEST_ENV = {
753
+ enabled: "ENABLE_A365_OBSERVABILITY_PER_REQUEST_EXPORT",
754
+ maxTraces: "A365_PER_REQUEST_MAX_TRACES",
755
+ maxSpansPerTrace: "A365_PER_REQUEST_MAX_SPANS_PER_TRACE",
756
+ maxConcurrentExports: "A365_PER_REQUEST_MAX_CONCURRENT_EXPORTS",
757
+ flushGraceMs: "A365_PER_REQUEST_FLUSH_GRACE_MS",
758
+ maxTraceAgeMs: "A365_PER_REQUEST_MAX_TRACE_AGE_MS"
759
+ };
760
+ function applyA365PerRequestEnvironment(configuration, env = process.env) {
761
+ if (!configuration) {
762
+ return;
763
+ }
764
+ setEnv(env, PER_REQUEST_ENV.enabled, formatBoolean(configuration.enabled));
765
+ setEnv(env, PER_REQUEST_ENV.maxTraces, configuration.maxTraces);
766
+ setEnv(env, PER_REQUEST_ENV.maxSpansPerTrace, configuration.maxSpansPerTrace);
767
+ setEnv(
768
+ env,
769
+ PER_REQUEST_ENV.maxConcurrentExports,
770
+ configuration.maxConcurrentExports
771
+ );
772
+ setEnv(env, PER_REQUEST_ENV.flushGraceMs, configuration.flushGraceMs);
773
+ setEnv(env, PER_REQUEST_ENV.maxTraceAgeMs, configuration.maxTraceAgeMs);
774
+ }
775
+ function setEnv(env, key, value) {
776
+ if (value === void 0) {
777
+ return;
778
+ }
779
+ env[key] = String(value);
780
+ }
781
+ function formatBoolean(value) {
782
+ return value === void 0 ? void 0 : value ? "true" : "false";
783
+ }
784
+
785
+ // src/runtime/lifecycle.ts
343
786
  async function initA365Observability(options) {
787
+ applyA365PerRequestEnvironment(options.configuration?.perRequest);
344
788
  const module = await loadObservabilityModule(options);
345
789
  const configProvider = createConfigurationProvider(
346
790
  module,
@@ -385,19 +829,6 @@ async function updateA365ExportToken(token, options = {}) {
385
829
  const module = await loadObservabilityModule(options);
386
830
  return module.updateExportToken?.(token) ?? false;
387
831
  }
388
- async function loadObservabilityModule(options = {}) {
389
- try {
390
- if (options.getObservabilityModule) {
391
- return await options.getObservabilityModule();
392
- }
393
- return await import(OBSERVABILITY_PACKAGE);
394
- } catch (error) {
395
- throw new A365ObservabilityModuleLoadError(
396
- `Unable to load ${OBSERVABILITY_PACKAGE}. Install @microsoft/agents-a365-observability and @microsoft/agents-a365-runtime before using @cuylabs/agent-a365-observability.`,
397
- { cause: error }
398
- );
399
- }
400
- }
401
832
  function createConfigurationProvider(module, configuration) {
402
833
  if (!configuration) {
403
834
  return void 0;
@@ -430,7 +861,7 @@ function normalizeCustomLogger(customLogger) {
430
861
  };
431
862
  }
432
863
 
433
- // src/turn-runner.ts
864
+ // src/context/turn-runner.ts
434
865
  async function runWithA365TurnContext(turnContext, optionsOrFn, maybeFnOrRuntime, maybeRuntime) {
435
866
  const options = typeof optionsOrFn === "function" ? {} : optionsOrFn ?? {};
436
867
  const fn = typeof optionsOrFn === "function" ? optionsOrFn : maybeFnOrRuntime;
@@ -444,14 +875,187 @@ async function runWithA365TurnContext(turnContext, optionsOrFn, maybeFnOrRuntime
444
875
  runtimeOptions
445
876
  );
446
877
  }
878
+
879
+ // src/runtime/s2s.ts
880
+ async function initA365S2SObservability(options) {
881
+ const env = resolveA365ObservabilityEnvironment(options.s2s?.env);
882
+ const tokenResolver = createA365S2STokenResolverFromEnv(options.s2s);
883
+ return await initA365Observability({
884
+ ...options,
885
+ tokenResolver,
886
+ exporterOptions: {
887
+ ...options.exporterOptions,
888
+ useS2SEndpoint: true
889
+ },
890
+ configuration: {
891
+ ...env.configuration,
892
+ ...options.configuration,
893
+ exporterEnabled: options.configuration?.exporterEnabled ?? env.configuration.exporterEnabled ?? true,
894
+ perRequest: {
895
+ ...env.configuration.perRequest,
896
+ ...options.configuration?.perRequest
897
+ }
898
+ }
899
+ });
900
+ }
901
+
902
+ // src/tracing/tracing-config.ts
903
+ function createA365TracingConfig(options = {}) {
904
+ const spanAttributes = {
905
+ ...options.spanAttributes ?? {}
906
+ };
907
+ if (isNonEmpty(options.tenantId)) {
908
+ spanAttributes[A365_BAGGAGE_KEYS.tenantId] = options.tenantId.trim();
909
+ }
910
+ return {
911
+ ...isNonEmpty(options.agentId) ? { agentId: options.agentId.trim() } : {},
912
+ ...isNonEmpty(options.agentDescription) ? { agentDescription: options.agentDescription.trim() } : {},
913
+ ...isNonEmpty(options.agentVersion) ? { agentVersion: options.agentVersion.trim() } : {},
914
+ ...options.useGenAIOpenTelemetry !== void 0 ? { useGenAIOpenTelemetry: options.useGenAIOpenTelemetry } : {},
915
+ ...options.telemetryIntegrations ? { telemetryIntegrations: options.telemetryIntegrations } : {},
916
+ ...options.useGlobalTelemetryIntegrations !== void 0 ? {
917
+ useGlobalTelemetryIntegrations: options.useGlobalTelemetryIntegrations
918
+ } : {},
919
+ ...Object.keys(spanAttributes).length > 0 ? { spanAttributes } : {}
920
+ };
921
+ }
922
+
923
+ // src/tracing/trace-context.ts
924
+ async function injectA365TraceContextToHeaders(headers = {}, options = {}) {
925
+ const module = await loadObservabilityModule(options);
926
+ if (!module.injectContextToHeaders) {
927
+ throw new Error(
928
+ "Agent 365 observability module does not export injectContextToHeaders."
929
+ );
930
+ }
931
+ return module.injectContextToHeaders(headers, options.context);
932
+ }
933
+ async function extractA365TraceContextFromHeaders(headers, options = {}) {
934
+ const module = await loadObservabilityModule(options);
935
+ if (!module.extractContextFromHeaders) {
936
+ throw new Error(
937
+ "Agent 365 observability module does not export extractContextFromHeaders."
938
+ );
939
+ }
940
+ return module.extractContextFromHeaders(headers, options.context);
941
+ }
942
+ async function runWithA365ExtractedTraceContext(headers, fn, options = {}) {
943
+ const module = await loadObservabilityModule(options);
944
+ if (!module.runWithExtractedTraceContext) {
945
+ throw new Error(
946
+ "Agent 365 observability module does not export runWithExtractedTraceContext."
947
+ );
948
+ }
949
+ return await module.runWithExtractedTraceContext(headers, fn);
950
+ }
951
+ async function runWithA365ParentSpanRef(parent, fn, options = {}) {
952
+ const module = await loadObservabilityModule(options);
953
+ if (!module.runWithParentSpanRef) {
954
+ throw new Error(
955
+ "Agent 365 observability module does not export runWithParentSpanRef."
956
+ );
957
+ }
958
+ return await module.runWithParentSpanRef(parent, fn);
959
+ }
960
+ async function createA365ContextWithParentSpanRef(baseContext, parent, options = {}) {
961
+ const module = await loadObservabilityModule(options);
962
+ if (!module.createContextWithParentSpanRef) {
963
+ throw new Error(
964
+ "Agent 365 observability module does not export createContextWithParentSpanRef."
965
+ );
966
+ }
967
+ return module.createContextWithParentSpanRef(baseContext, parent);
968
+ }
969
+
970
+ // src/tracing/output-scope.ts
971
+ async function runWithA365OutputMessages(output, fn, options = {}) {
972
+ const module = await loadObservabilityModule(options);
973
+ if (!module.OutputScope) {
974
+ throw new Error(
975
+ "Agent 365 observability module does not export OutputScope."
976
+ );
977
+ }
978
+ const outputScope = module.OutputScope.start(
979
+ createOutputRequest(output),
980
+ { messages: output.messages },
981
+ createAgentDetails(output),
982
+ createUserDetails(output),
983
+ output.parentSpan ? { parentContext: output.parentSpan } : void 0
984
+ );
985
+ try {
986
+ return await fn();
987
+ } catch (error) {
988
+ outputScope.recordError?.(normalizeError(error));
989
+ throw error;
990
+ } finally {
991
+ outputScope.dispose();
992
+ }
993
+ }
994
+ function createOutputRequest(context) {
995
+ return {
996
+ conversationId: context.conversationId,
997
+ sessionId: context.sessionId,
998
+ channel: context.channelName || context.channelLink ? {
999
+ name: context.channelName,
1000
+ description: context.channelLink
1001
+ } : void 0
1002
+ };
1003
+ }
1004
+ function createAgentDetails(context) {
1005
+ return {
1006
+ tenantId: context.tenantId,
1007
+ agentId: context.agentId,
1008
+ agentName: context.agentName,
1009
+ agentDescription: context.agentDescription,
1010
+ agentVersion: context.agentVersion,
1011
+ agentPlatformId: context.agentPlatformId,
1012
+ agentAUID: context.agentAuid,
1013
+ agentEmail: context.agentEmail,
1014
+ agentBlueprintId: context.agentBlueprintId
1015
+ };
1016
+ }
1017
+ function createUserDetails(context) {
1018
+ if (!context.userId && !context.userName && !context.userEmail) {
1019
+ return void 0;
1020
+ }
1021
+ return {
1022
+ tenantId: context.tenantId,
1023
+ userId: context.userId,
1024
+ userName: context.userName,
1025
+ userEmail: context.userEmail
1026
+ };
1027
+ }
1028
+ function normalizeError(error) {
1029
+ if (error instanceof Error) {
1030
+ return error;
1031
+ }
1032
+ return new Error(typeof error === "string" ? error : JSON.stringify(error));
1033
+ }
447
1034
  export {
1035
+ A365ObservabilityHostingModuleLoadError,
448
1036
  A365ObservabilityModuleLoadError,
449
1037
  A365_BAGGAGE_KEYS,
1038
+ A365_EXPORTER_EVENT_NAMES,
1039
+ applyA365PerRequestEnvironment,
450
1040
  buildA365BaggagePairs,
451
1041
  createA365ContextFromTurnContext,
1042
+ createA365ContextWithParentSpanRef,
1043
+ createA365OboTokenResolver,
1044
+ createA365S2STokenResolver,
1045
+ createA365S2STokenResolverFromEnv,
452
1046
  createA365TracingConfig,
1047
+ extractA365TraceContextFromHeaders,
453
1048
  initA365Observability,
1049
+ initA365S2SObservability,
1050
+ injectA365TraceContextToHeaders,
1051
+ invalidateA365OboObservabilityToken,
1052
+ invalidateAllA365OboObservabilityTokens,
1053
+ refreshA365OboObservabilityToken,
1054
+ resolveA365ObservabilityEnvironment,
454
1055
  runWithA365Context,
1056
+ runWithA365ExtractedTraceContext,
1057
+ runWithA365OutputMessages,
1058
+ runWithA365ParentSpanRef,
455
1059
  runWithA365TurnContext,
456
1060
  updateA365ExportToken
457
1061
  };