@cuylabs/agent-microsoft-opentelemetry 4.8.0 → 4.8.1

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/README.md CHANGED
@@ -15,10 +15,11 @@ Microsoft now publishes a consolidated OpenTelemetry distro at
15
15
  setup, Agent 365 exporter, Agent 365 baggage processor, Azure Monitor exporter,
16
16
  OTLP support, and supported Microsoft framework auto-instrumentation.
17
17
 
18
- `@cuylabs/agent-core` already emits AI SDK v7 GenAI telemetry for model and tool
19
- execution. This package bridges those agent-core spans into the Microsoft distro
20
- pipeline and supplies Agent 365 identity context, S2S token resolution, and
21
- request-scoped baggage.
18
+ `@cuylabs/agent-core` owns the agent runtime, but Agent 365 has product-specific
19
+ scope types for invocation, inference, tools, and output. This package bridges
20
+ those two models: it starts official Microsoft A365 scopes from agent-core
21
+ lifecycle events, runs turns with Agent 365 baggage, provides S2S token
22
+ resolution, and lets the Microsoft distro export the resulting spans.
22
23
 
23
24
  ## Install
24
25
 
@@ -60,24 +61,31 @@ const observability = await initMicrosoftOpenTelemetry({
60
61
  });
61
62
  ```
62
63
 
63
- Pass `observability.tracing` to `createAgent({ tracing })`. Wrap channel turn
64
- sources with `createMicrosoftA365ObservedTurnSource()` so each turn runs with
65
- Agent 365 baggage, including tenant, agent, user, channel, conversation, and
66
- session attributes.
64
+ Pass `observability.tracing` to `createAgent({ tracing })`. By default that
65
+ tracing config installs the package's official A365 scope middleware:
66
+
67
+ - `InvokeAgentScope` starts when the agent turn starts.
68
+ - `InferenceScope` starts around each model step.
69
+ - `ExecuteToolScope` starts for each agent-core tool call event.
70
+ - `OutputScope` is emitted at turn completion.
71
+
72
+ Wrap channel turn sources with `createMicrosoftA365ObservedTurnSource()` so each
73
+ turn runs with Agent 365 baggage, including tenant, agent, user, channel,
74
+ conversation, and session attributes.
67
75
 
68
76
  ## Environment Compatibility
69
77
 
70
78
  The S2S helpers understand the Agent 365 CLI-generated settings:
71
79
 
72
- | Variable | Purpose |
73
- | --- | --- |
74
- | `A365_OBSERVABILITY_TENANT_ID` or `agent365Observability__tenantId` | Tenant that owns the Agent 365 identity. |
75
- | `A365_OBSERVABILITY_AGENT_ID` or `agent365Observability__agentId` | Agent 365 agent identity service principal ID. |
76
- | `A365_OBSERVABILITY_CLIENT_ID` or `agent365Observability__clientId` | Blueprint application/client ID. |
80
+ | Variable | Purpose |
81
+ | --------------------------------------------------------------------------- | --------------------------------------------------------------- |
82
+ | `A365_OBSERVABILITY_TENANT_ID` or `agent365Observability__tenantId` | Tenant that owns the Agent 365 identity. |
83
+ | `A365_OBSERVABILITY_AGENT_ID` or `agent365Observability__agentId` | Agent 365 agent identity service principal ID. |
84
+ | `A365_OBSERVABILITY_CLIENT_ID` or `agent365Observability__clientId` | Blueprint application/client ID. |
77
85
  | `A365_OBSERVABILITY_CLIENT_SECRET` or `agent365Observability__clientSecret` | Blueprint client secret. Store this in Key Vault in production. |
78
- | `ENABLE_A365_OBSERVABILITY_EXPORTER` | Enables export to Agent 365. |
79
- | `A365_OBSERVABILITY_USE_S2S_ENDPOINT` | Uses the S2S Agent 365 ingestion path. |
80
- | `A365_OBSERVABILITY_LOG_LEVEL` | Microsoft distro Agent 365 log level. |
86
+ | `ENABLE_A365_OBSERVABILITY_EXPORTER` | Enables export to Agent 365. |
87
+ | `A365_OBSERVABILITY_USE_S2S_ENDPOINT` | Uses the S2S Agent 365 ingestion path. |
88
+ | `A365_OBSERVABILITY_LOG_LEVEL` | Microsoft distro Agent 365 log level. |
81
89
 
82
90
  The package also accepts standard OpenTelemetry resource variables such as
83
91
  `OTEL_SERVICE_NAME` and `OTEL_SERVICE_VERSION`.
@@ -116,7 +124,7 @@ initialization without changing the command line.
116
124
  The adapter is `agent-core` first. Its default profile is `agent-core`, which
117
125
  keeps Microsoft exporters and processors active while disabling Microsoft's
118
126
  optional OpenAI Agents SDK and LangChain auto-instrumentations. `agent-core`
119
- already emits the AI SDK v7 telemetry used by `agents-ts`.
127
+ execution is represented through the adapter's official A365 scope middleware.
120
128
 
121
129
  Use `instrumentationProfile: "microsoft-genai"` only when the same Node process
122
130
  also runs Microsoft's supported OpenAI Agents SDK or LangChain integrations. Use
@@ -146,25 +154,32 @@ adapters should use `createMicrosoftA365ObservedTurnSource()` instead.
146
154
 
147
155
  ## Package Boundary
148
156
 
149
- This package does not replace `agent-core` telemetry. Instead:
157
+ This package does not replace `agent-core` execution. Instead:
158
+
159
+ - `agent-core` runs the turn, model loop, tools, approvals, and event stream.
160
+ - This package converts those lifecycle hooks into official A365 scopes.
161
+ - `@microsoft/opentelemetry` owns the provider, span processors, exporters,
162
+ Azure Monitor, OTLP, and Agent 365 HTTP exporter.
150
163
 
151
- - `agent-core` creates the agent invocation/model/tool spans.
152
- - `@microsoft/opentelemetry` owns the provider/exporter pipeline.
153
- - This package maps Agent 365 identity and request metadata into baggage and
154
- returns `agent-core` tracing defaults that avoid duplicate tool spans.
164
+ The default tracing config disables agent-core's older generic OTel middleware
165
+ and the local AI SDK GenAI integration so one `agents-ts` turn produces one
166
+ official A365 scope tree. Applications can opt back into those lower-level
167
+ spans with `useA365Scopes: false`, `useDefaultOtelMiddleware: true`, or
168
+ `useGenAIOpenTelemetry: true`, but those settings should be deliberate because
169
+ they can duplicate tool or model telemetry.
155
170
 
156
171
  ## Source Layout
157
172
 
158
173
  The source tree mirrors the adapter's ownership boundaries, not the full
159
174
  Microsoft distro implementation:
160
175
 
161
- | Folder | Owns |
162
- | --- | --- |
163
- | `src/a365` | Agent 365 request context, baggage attributes, and turn-source wrappers. |
164
- | `src/auth` | S2S token exchange and Agent 365 CLI environment compatibility. |
165
- | `src/runtime` | Microsoft distro startup, loader integration, destination helpers, instrumentation profiles, and exporter options. |
166
- | `src/tracing` | `agent-core` tracing defaults for Microsoft-backed pipelines. |
167
- | `src/common` and `src/internal` | Shared public types and package-private utilities. |
176
+ | Folder | Owns |
177
+ | ------------------------------- | ------------------------------------------------------------------------------------------------------------------ |
178
+ | `src/a365` | Agent 365 request context, baggage attributes, official scope middleware, and turn-source wrappers. |
179
+ | `src/auth` | S2S token exchange and Agent 365 CLI environment compatibility. |
180
+ | `src/runtime` | Microsoft distro startup, loader integration, destination helpers, instrumentation profiles, and exporter options. |
181
+ | `src/tracing` | `agent-core` tracing defaults for Microsoft-backed pipelines. |
182
+ | `src/common` and `src/internal` | Shared public types and package-private utilities. |
168
183
 
169
184
  For details, see [docs/architecture.md](./docs/architecture.md).
170
185
  For a direct mapping to Microsoft's distro documentation, see
@@ -610,6 +610,16 @@ function buildMicrosoftA365BaggagePairs(context) {
610
610
  return pairs;
611
611
  }
612
612
 
613
+ // src/a365/context-store.ts
614
+ import { AsyncLocalStorage } from "async_hooks";
615
+ var microsoftA365RequestContextStorage = new AsyncLocalStorage();
616
+ function currentMicrosoftA365RequestContext() {
617
+ return microsoftA365RequestContextStorage.getStore();
618
+ }
619
+ function runWithMicrosoftA365RequestContext(requestContext, fn) {
620
+ return microsoftA365RequestContextStorage.run(requestContext, fn);
621
+ }
622
+
613
623
  // src/runtime/module-loader.ts
614
624
  var MICROSOFT_OPENTELEMETRY_PACKAGE = "@microsoft/opentelemetry";
615
625
  var OTEL_RESOURCES_PACKAGE = "@opentelemetry/resources";
@@ -640,6 +650,400 @@ async function loadOpenTelemetryResourceModule(options = {}) {
640
650
  }
641
651
  }
642
652
 
653
+ // src/a365/scope-middleware.ts
654
+ import { context as otelContext } from "@opentelemetry/api";
655
+ function createMicrosoftA365ScopeMiddleware(options = {}) {
656
+ const recordInputs = options.recordInputs ?? true;
657
+ const recordOutputs = options.recordOutputs ?? true;
658
+ const emitOutputScope = options.emitOutputScope ?? true;
659
+ const states = /* @__PURE__ */ new Map();
660
+ const statesByRequestContext = /* @__PURE__ */ new WeakMap();
661
+ return {
662
+ name: "microsoft-a365-scopes",
663
+ async onChatStart(sessionId, message, ctx) {
664
+ const requestContext = currentMicrosoftA365RequestContext();
665
+ if (!requestContext || !hasA365ScopeIdentity(requestContext)) {
666
+ return;
667
+ }
668
+ const module = await loadMicrosoftOpenTelemetryModule(options.runtime);
669
+ if (!module.InvokeAgentScope) {
670
+ return;
671
+ }
672
+ const scopedRequestContext = {
673
+ ...requestContext,
674
+ sessionId: requestContext.sessionId ?? sessionId,
675
+ conversationId: requestContext.conversationId ?? sessionId
676
+ };
677
+ const scope = safeStartScope(
678
+ () => module.InvokeAgentScope.start(
679
+ createRequest(scopedRequestContext, {
680
+ sessionId,
681
+ content: recordInputs ? message : void 0
682
+ }),
683
+ createInvokeScopeDetails(scopedRequestContext),
684
+ createAgentDetails(scopedRequestContext),
685
+ createCallerDetails(scopedRequestContext)
686
+ )
687
+ );
688
+ if (!scope) {
689
+ return;
690
+ }
691
+ const state = {
692
+ module,
693
+ requestContext: scopedRequestContext,
694
+ invokeScope: scope,
695
+ invokeContext: createOtelContext(module, scope.getSpanContext()),
696
+ inferenceScopes: /* @__PURE__ */ new Map(),
697
+ toolScopes: /* @__PURE__ */ new Map()
698
+ };
699
+ states.set(turnKey(sessionId, ctx?.turnId), state);
700
+ statesByRequestContext.set(requestContext, state);
701
+ statesByRequestContext.set(scopedRequestContext, state);
702
+ },
703
+ model: {
704
+ async input(input, ctx) {
705
+ const state = findState(states, ctx.sessionID, ctx.turnID);
706
+ if (!state?.module.InferenceScope) {
707
+ return void 0;
708
+ }
709
+ const key = modelKey(ctx);
710
+ disposeInferenceScope(state, key);
711
+ const messages = recordInputs ? inputMessages(input) : void 0;
712
+ const scope = safeStartScope(
713
+ () => state.module.InferenceScope.start(
714
+ createRequest(state.requestContext, {
715
+ sessionId: ctx.sessionID,
716
+ content: messages
717
+ }),
718
+ {
719
+ operationName: state.module.InferenceOperationType?.CHAT ?? "Chat",
720
+ model: modelName(input.model),
721
+ providerName: providerName(input.model)
722
+ },
723
+ createAgentDetails(state.requestContext),
724
+ createUserDetails(state.requestContext),
725
+ createSpanDetails(state.invokeScope)
726
+ )
727
+ );
728
+ if (!scope) {
729
+ return void 0;
730
+ }
731
+ if (messages && messages.length > 0) {
732
+ scope.recordInputMessages?.(messages);
733
+ }
734
+ state.inferenceScopes.set(key, {
735
+ scope,
736
+ otelContext: createOtelContext(state.module, scope.getSpanContext())
737
+ });
738
+ return void 0;
739
+ },
740
+ async output(output, ctx) {
741
+ const state = findState(states, ctx.sessionID, ctx.turnID);
742
+ const scopeState = state?.inferenceScopes.get(modelKey(ctx));
743
+ const scope = scopeState?.scope;
744
+ if (!state || !scope) {
745
+ return void 0;
746
+ }
747
+ if (recordOutputs && output.text) {
748
+ scope.recordOutputMessages?.(output.text);
749
+ }
750
+ if (output.usage?.inputTokens !== void 0) {
751
+ scope.recordInputTokens?.(output.usage.inputTokens);
752
+ }
753
+ if (output.usage?.outputTokens !== void 0) {
754
+ scope.recordOutputTokens?.(output.usage.outputTokens);
755
+ }
756
+ if (output.finishReason) {
757
+ scope.recordFinishReasons?.([output.finishReason]);
758
+ }
759
+ disposeInferenceScope(state, modelKey(ctx));
760
+ return output;
761
+ }
762
+ },
763
+ onEvent(event) {
764
+ const state = currentState(states, statesByRequestContext);
765
+ if (!state) {
766
+ return;
767
+ }
768
+ if (event.type === "tool-start") {
769
+ startToolScope(state, event, recordInputs);
770
+ return;
771
+ }
772
+ if (event.type === "tool-result") {
773
+ finishToolScope(state, event, recordOutputs);
774
+ return;
775
+ }
776
+ if (event.type === "tool-error") {
777
+ finishToolScopeWithError(state, event);
778
+ return;
779
+ }
780
+ if (event.type === "error") {
781
+ for (const key of state.inferenceScopes.keys()) {
782
+ const scope = state.inferenceScopes.get(key)?.scope;
783
+ scope?.recordError(event.error);
784
+ scope?.dispose();
785
+ }
786
+ state.inferenceScopes.clear();
787
+ }
788
+ },
789
+ async onChatEnd(sessionId, result, ctx) {
790
+ const key = turnKey(sessionId, ctx?.turnId);
791
+ const state = states.get(key) ?? currentState(states, statesByRequestContext);
792
+ if (!state) {
793
+ return;
794
+ }
795
+ for (const scopeState of state.inferenceScopes.values()) {
796
+ if (result.error) {
797
+ scopeState.scope.recordError(result.error);
798
+ }
799
+ scopeState.scope.dispose();
800
+ }
801
+ state.inferenceScopes.clear();
802
+ for (const scope of state.toolScopes.values()) {
803
+ if (result.error) {
804
+ scope.recordError(result.error);
805
+ }
806
+ scope.dispose();
807
+ }
808
+ state.toolScopes.clear();
809
+ if (result.error) {
810
+ state.invokeScope?.recordError(result.error);
811
+ } else if (recordOutputs && result.output) {
812
+ state.invokeScope?.recordResponse?.(result.output);
813
+ }
814
+ if (emitOutputScope && recordOutputs && result.output && state.module.OutputScope) {
815
+ const outputScope = safeStartScope(
816
+ () => state.module.OutputScope.start(
817
+ createRequest(state.requestContext, { sessionId }),
818
+ { messages: result.output },
819
+ createAgentDetails(state.requestContext),
820
+ createUserDetails(state.requestContext),
821
+ createSpanDetails(state.invokeScope)
822
+ )
823
+ );
824
+ outputScope?.dispose();
825
+ }
826
+ state.invokeScope?.dispose();
827
+ states.delete(key);
828
+ const requestContext = currentMicrosoftA365RequestContext();
829
+ if (requestContext) {
830
+ statesByRequestContext.delete(requestContext);
831
+ }
832
+ statesByRequestContext.delete(state.requestContext);
833
+ },
834
+ getOtelContext(sessionId, ctx) {
835
+ const state = findState(states, sessionId, ctx?.turnId);
836
+ if (!state) {
837
+ return void 0;
838
+ }
839
+ const inferenceContext = latestInferenceContext(state);
840
+ return inferenceContext ?? state.invokeContext;
841
+ }
842
+ };
843
+ }
844
+ function hasA365ScopeIdentity(context) {
845
+ return Boolean(context.tenantId && context.agentId);
846
+ }
847
+ function safeStartScope(start) {
848
+ try {
849
+ return start();
850
+ } catch {
851
+ return void 0;
852
+ }
853
+ }
854
+ function currentState(states, statesByRequestContext) {
855
+ const requestContext = currentMicrosoftA365RequestContext();
856
+ if (requestContext) {
857
+ const state = statesByRequestContext.get(requestContext);
858
+ if (state) {
859
+ return state;
860
+ }
861
+ if (requestContext.sessionId) {
862
+ return findState(states, requestContext.sessionId);
863
+ }
864
+ }
865
+ return latestState(states);
866
+ }
867
+ function startToolScope(state, event, recordInputs) {
868
+ if (!state.module.ExecuteToolScope) {
869
+ return;
870
+ }
871
+ const scope = safeStartScope(
872
+ () => state.module.ExecuteToolScope.start(
873
+ createRequest(state.requestContext),
874
+ {
875
+ toolName: event.toolName,
876
+ arguments: recordInputs ? safeJsonStringify(event.input) : void 0,
877
+ toolCallId: event.toolCallId,
878
+ toolType: "function"
879
+ },
880
+ createAgentDetails(state.requestContext),
881
+ createUserDetails(state.requestContext),
882
+ createSpanDetails(latestInferenceScope(state) ?? state.invokeScope)
883
+ )
884
+ );
885
+ if (!scope) {
886
+ return;
887
+ }
888
+ state.toolScopes.set(toolKey(event.toolName, event.toolCallId), scope);
889
+ }
890
+ function finishToolScope(state, event, recordOutputs) {
891
+ const key = toolKey(event.toolName, event.toolCallId);
892
+ const scope = state.toolScopes.get(key);
893
+ if (!scope) {
894
+ return;
895
+ }
896
+ if (recordOutputs) {
897
+ scope.recordResponse?.(safeJsonStringify(event.result));
898
+ }
899
+ scope.dispose();
900
+ state.toolScopes.delete(key);
901
+ }
902
+ function finishToolScopeWithError(state, event) {
903
+ const key = toolKey(event.toolName, event.toolCallId);
904
+ const scope = state.toolScopes.get(key);
905
+ if (!scope) {
906
+ return;
907
+ }
908
+ scope.recordError(new Error(event.error));
909
+ scope.dispose();
910
+ state.toolScopes.delete(key);
911
+ }
912
+ function disposeInferenceScope(state, key) {
913
+ const scope = state.inferenceScopes.get(key)?.scope;
914
+ if (scope) {
915
+ scope.dispose();
916
+ state.inferenceScopes.delete(key);
917
+ }
918
+ }
919
+ function latestInferenceScope(state) {
920
+ return Array.from(state.inferenceScopes.values()).at(-1)?.scope;
921
+ }
922
+ function latestInferenceContext(state) {
923
+ return Array.from(state.inferenceScopes.values()).at(-1)?.otelContext;
924
+ }
925
+ function createSpanDetails(scope) {
926
+ return scope ? { parentContext: scope.getSpanContext() } : void 0;
927
+ }
928
+ function createOtelContext(module, parent) {
929
+ return module.createContextWithParentSpanRef?.(otelContext.active(), parent);
930
+ }
931
+ function createRequest(context, options = {}) {
932
+ return {
933
+ ...options.content !== void 0 ? { content: options.content } : {},
934
+ sessionId: options.sessionId ?? context.sessionId,
935
+ conversationId: context.conversationId ?? options.sessionId,
936
+ ...context.channelName || context.channelLink ? {
937
+ channel: {
938
+ name: context.channelName,
939
+ description: context.channelLink
940
+ }
941
+ } : {}
942
+ };
943
+ }
944
+ function createInvokeScopeDetails(context) {
945
+ return {
946
+ ...context.serverAddress ? {
947
+ endpoint: {
948
+ host: context.serverAddress,
949
+ ...context.serverPort ? { port: context.serverPort } : {}
950
+ }
951
+ } : {}
952
+ };
953
+ }
954
+ function createAgentDetails(context) {
955
+ return {
956
+ agentId: context.agentId ?? "unknown",
957
+ agentName: context.agentName,
958
+ agentDescription: context.agentDescription,
959
+ agentVersion: context.agentVersion,
960
+ platformId: context.agentPlatformId,
961
+ agentAUID: context.agentAuid,
962
+ agentEmail: context.agentEmail,
963
+ agentBlueprintId: context.agentBlueprintId,
964
+ tenantId: context.tenantId
965
+ };
966
+ }
967
+ function createCallerDetails(context) {
968
+ const userDetails = createUserDetails(context);
969
+ const callerAgentDetails = context.callerAgentId || context.callerAgentName ? {
970
+ agentId: context.callerAgentId ?? "unknown",
971
+ agentName: context.callerAgentName,
972
+ agentAUID: context.callerAgentAuid,
973
+ agentEmail: context.callerAgentEmail,
974
+ agentBlueprintId: context.callerAgentBlueprintId,
975
+ platformId: context.callerAgentPlatformId,
976
+ agentVersion: context.callerAgentVersion,
977
+ tenantId: context.tenantId
978
+ } : void 0;
979
+ return userDetails || callerAgentDetails ? {
980
+ ...userDetails ? { userDetails } : {},
981
+ ...callerAgentDetails ? { callerAgentDetails } : {}
982
+ } : void 0;
983
+ }
984
+ function createUserDetails(context) {
985
+ return context.userId || context.userEmail || context.userName || context.callerClientIp ? {
986
+ userId: context.userId,
987
+ userEmail: context.userEmail,
988
+ userName: context.userName,
989
+ callerClientIp: context.callerClientIp
990
+ } : void 0;
991
+ }
992
+ function inputMessages(input) {
993
+ return [
994
+ ...input.system,
995
+ ...input.messages.map((message) => safeJsonStringify(message))
996
+ ].filter(Boolean);
997
+ }
998
+ function modelName(model) {
999
+ if (model && typeof model === "object" && "modelId" in model) {
1000
+ return String(model.modelId ?? "unknown");
1001
+ }
1002
+ return String(model ?? "unknown");
1003
+ }
1004
+ function providerName(model) {
1005
+ if (model && typeof model === "object" && "provider" in model) {
1006
+ return String(model.provider ?? "") || void 0;
1007
+ }
1008
+ return void 0;
1009
+ }
1010
+ function safeJsonStringify(value) {
1011
+ if (typeof value === "string") {
1012
+ return value;
1013
+ }
1014
+ try {
1015
+ return JSON.stringify(value) ?? String(value);
1016
+ } catch {
1017
+ return String(value);
1018
+ }
1019
+ }
1020
+ function turnKey(sessionId, turnId) {
1021
+ return `${sessionId}:${turnId ?? ""}`;
1022
+ }
1023
+ function modelKey(ctx) {
1024
+ return `${turnKey(ctx.sessionID, ctx.turnID)}:${ctx.step}`;
1025
+ }
1026
+ function toolKey(toolName, toolCallId) {
1027
+ return `${toolName}:${toolCallId}`;
1028
+ }
1029
+ function findState(states, sessionId, turnId) {
1030
+ const exact = states.get(turnKey(sessionId, turnId)) ?? states.get(turnKey(sessionId));
1031
+ if (exact || turnId) {
1032
+ return exact;
1033
+ }
1034
+ const prefix = `${sessionId}:`;
1035
+ let latest;
1036
+ for (const [key, state] of states) {
1037
+ if (key.startsWith(prefix)) {
1038
+ latest = state;
1039
+ }
1040
+ }
1041
+ return latest;
1042
+ }
1043
+ function latestState(states) {
1044
+ return Array.from(states.values()).at(-1);
1045
+ }
1046
+
643
1047
  // src/tracing/tracing-config.ts
644
1048
  function createMicrosoftOpenTelemetryTracingConfig(options = {}) {
645
1049
  const spanAttributes = {
@@ -648,6 +1052,7 @@ function createMicrosoftOpenTelemetryTracingConfig(options = {}) {
648
1052
  if (isNonEmpty(options.tenantId)) {
649
1053
  spanAttributes[MICROSOFT_A365_ATTRIBUTES.tenantId] = options.tenantId.trim();
650
1054
  }
1055
+ const useA365Scopes = options.useA365Scopes ?? true;
651
1056
  return {
652
1057
  ...isNonEmpty(options.agentId) ? { agentId: options.agentId.trim() } : {},
653
1058
  ...isNonEmpty(options.agentDescription) ? { agentDescription: options.agentDescription.trim() } : {},
@@ -655,11 +1060,15 @@ function createMicrosoftOpenTelemetryTracingConfig(options = {}) {
655
1060
  ...options.recordInputs !== void 0 ? { recordInputs: options.recordInputs } : {},
656
1061
  ...options.recordOutputs !== void 0 ? { recordOutputs: options.recordOutputs } : {},
657
1062
  emitToolSpans: options.emitToolSpans ?? false,
658
- ...options.useGenAIOpenTelemetry !== void 0 ? { useGenAIOpenTelemetry: options.useGenAIOpenTelemetry } : {},
1063
+ useGenAIOpenTelemetry: options.useGenAIOpenTelemetry ?? (useA365Scopes ? false : true),
659
1064
  ...options.telemetryIntegrations ? { telemetryIntegrations: options.telemetryIntegrations } : {},
660
1065
  ...options.useGlobalTelemetryIntegrations !== void 0 ? {
661
1066
  useGlobalTelemetryIntegrations: options.useGlobalTelemetryIntegrations
662
1067
  } : {},
1068
+ ...useA365Scopes ? {
1069
+ middleware: createMicrosoftA365ScopeMiddleware(options.a365Scopes),
1070
+ useDefaultOtelMiddleware: options.useDefaultOtelMiddleware ?? false
1071
+ } : options.useDefaultOtelMiddleware !== void 0 ? { useDefaultOtelMiddleware: options.useDefaultOtelMiddleware } : {},
663
1072
  ...Object.keys(spanAttributes).length > 0 ? { spanAttributes } : {}
664
1073
  };
665
1074
  }
@@ -793,10 +1202,10 @@ async function initMicrosoftOpenTelemetryFromEnv(options = {}) {
793
1202
  }
794
1203
  async function runWithMicrosoftA365Context(requestContext, fn, options = {}) {
795
1204
  const module = await loadMicrosoftOpenTelemetryModule(options);
796
- const runWithBaggage = () => {
1205
+ const runWithBaggage = () => runWithMicrosoftA365RequestContext(requestContext, () => {
797
1206
  const scope = new module.BaggageBuilder().setPairs(buildMicrosoftA365BaggagePairs(requestContext)).build();
798
1207
  return scope.run(fn);
799
- };
1208
+ });
800
1209
  const result = requestContext.exportToken && module.runWithExportToken ? module.runWithExportToken(requestContext.exportToken, runWithBaggage) : runWithBaggage();
801
1210
  return await result;
802
1211
  }
@@ -951,8 +1360,11 @@ export {
951
1360
  createEnvReader,
952
1361
  MICROSOFT_A365_ATTRIBUTES,
953
1362
  buildMicrosoftA365BaggagePairs,
1363
+ currentMicrosoftA365RequestContext,
1364
+ runWithMicrosoftA365RequestContext,
954
1365
  loadMicrosoftOpenTelemetryModule,
955
1366
  loadOpenTelemetryResourceModule,
1367
+ createMicrosoftA365ScopeMiddleware,
956
1368
  createMicrosoftOpenTelemetryTracingConfig,
957
1369
  createMicrosoftOpenTelemetryInstrumentationOptions,
958
1370
  normalizeInstrumentationProfile,
package/dist/index.d.ts CHANGED
@@ -1,5 +1,5 @@
1
1
  import { TelemetryOptions } from 'ai';
2
- import { AgentTurnSourceChatOptions, AgentTurnSource } from '@cuylabs/agent-core';
2
+ import { AgentMiddleware, AgentTurnSourceChatOptions, AgentTurnSource } from '@cuylabs/agent-core';
3
3
 
4
4
  type MicrosoftOpenTelemetryTokenResolver = (agentId: string, tenantId: string, authScopes?: string[]) => string | null | Promise<string | null>;
5
5
  type MicrosoftOpenTelemetryLogger = {
@@ -25,6 +25,13 @@ type MicrosoftOpenTelemetryTracingConfigOptions = {
25
25
  telemetryIntegrations?: TelemetryOptions["integrations"];
26
26
  useGlobalTelemetryIntegrations?: boolean;
27
27
  spanAttributes?: Record<string, SpanAttributeValue>;
28
+ /**
29
+ * Use official Agent 365 scopes for invoke, inference, tool, and output
30
+ * telemetry. Defaults to true.
31
+ */
32
+ useA365Scopes?: boolean;
33
+ a365Scopes?: Omit<MicrosoftA365ScopeMiddlewareOptions, "runtime">;
34
+ useDefaultOtelMiddleware?: boolean;
28
35
  };
29
36
  type MicrosoftOpenTelemetryTracingConfig = {
30
37
  agentId?: string;
@@ -37,6 +44,8 @@ type MicrosoftOpenTelemetryTracingConfig = {
37
44
  telemetryIntegrations?: TelemetryOptions["integrations"];
38
45
  useGlobalTelemetryIntegrations?: boolean;
39
46
  spanAttributes?: Record<string, SpanAttributeValue>;
47
+ middleware?: AgentMiddleware | AgentMiddleware[];
48
+ useDefaultOtelMiddleware?: boolean;
40
49
  };
41
50
 
42
51
  type MicrosoftOpenTelemetryResourceOptions = {
@@ -145,6 +154,16 @@ type MicrosoftOpenTelemetryDistroModule = {
145
154
  useMicrosoftOpenTelemetry(options?: Record<string, unknown>): void;
146
155
  shutdownMicrosoftOpenTelemetry(): Promise<void>;
147
156
  BaggageBuilder: new () => MicrosoftBaggageBuilderLike;
157
+ InvokeAgentScope?: MicrosoftA365InvokeAgentScopeConstructor;
158
+ ExecuteToolScope?: MicrosoftA365ExecuteToolScopeConstructor;
159
+ InferenceScope?: MicrosoftA365InferenceScopeConstructor;
160
+ OutputScope?: MicrosoftA365OutputScopeConstructor;
161
+ InferenceOperationType?: {
162
+ CHAT?: string;
163
+ TEXT_COMPLETION?: string;
164
+ GENERATE_CONTENT?: string;
165
+ };
166
+ createContextWithParentSpanRef?: (baseContext: unknown, parent: MicrosoftA365ParentSpanRef) => unknown;
148
167
  runWithExportToken?: <T>(token: string, fn: () => T) => T;
149
168
  updateExportToken?: (token: string) => boolean;
150
169
  configureA365Hosting?: (adapter: MicrosoftA365HostingAdapterLike, options?: MicrosoftA365HostingOptions) => unknown;
@@ -161,6 +180,35 @@ type MicrosoftBaggageBuilderLike = {
161
180
  run<T>(fn: () => T): T;
162
181
  };
163
182
  };
183
+ type MicrosoftA365ParentSpanRef = {
184
+ traceId: string;
185
+ spanId: string;
186
+ traceFlags?: number;
187
+ traceState?: unknown;
188
+ isRemote?: boolean;
189
+ };
190
+ type MicrosoftA365ScopeLike = {
191
+ getSpanContext(): MicrosoftA365ParentSpanRef;
192
+ withActiveSpanAsync?<T>(callback: () => Promise<T>): Promise<T>;
193
+ recordError(error: Error): void;
194
+ recordInputMessages?(messages: string | string[]): void;
195
+ recordOutputMessages?(messages: string | string[]): void;
196
+ recordResponse?(response: string | Record<string, unknown>): void;
197
+ recordInputTokens?(tokens: number): void;
198
+ recordOutputTokens?(tokens: number): void;
199
+ recordFinishReasons?(reasons: string[]): void;
200
+ dispose(): void;
201
+ };
202
+ type MicrosoftA365InvokeAgentScopeConstructor = {
203
+ start(request: Record<string, unknown>, details: Record<string, unknown>, agentDetails: Record<string, unknown>, callerDetails?: Record<string, unknown>, spanDetails?: Record<string, unknown>): MicrosoftA365ScopeLike;
204
+ };
205
+ type MicrosoftA365ExecuteToolScopeConstructor = {
206
+ start(request: Record<string, unknown>, details: Record<string, unknown>, agentDetails: Record<string, unknown>, userDetails?: Record<string, unknown>, spanDetails?: Record<string, unknown>): MicrosoftA365ScopeLike;
207
+ };
208
+ type MicrosoftA365InferenceScopeConstructor = MicrosoftA365ExecuteToolScopeConstructor;
209
+ type MicrosoftA365OutputScopeConstructor = {
210
+ start(request: Record<string, unknown>, response: Record<string, unknown>, agentDetails: Record<string, unknown>, userDetails?: Record<string, unknown>, spanDetails?: Record<string, unknown>): MicrosoftA365ScopeLike;
211
+ };
164
212
 
165
213
  type MicrosoftOpenTelemetryA365Options = {
166
214
  /**
@@ -248,6 +296,25 @@ type MicrosoftA365RequestContext = {
248
296
  exportToken?: string;
249
297
  extraBaggage?: Record<string, string | number | boolean | null | undefined>;
250
298
  };
299
+ type MicrosoftA365ScopeMiddlewareOptions = {
300
+ /**
301
+ * Capture prompt/user content on invoke and inference scopes.
302
+ *
303
+ * Defaults to true to match Agent 365 validation expectations. Disable for
304
+ * deployments that must export metadata only.
305
+ */
306
+ recordInputs?: boolean;
307
+ /**
308
+ * Capture response/tool result content on invoke, inference, tool, and output
309
+ * scopes. Defaults to true.
310
+ */
311
+ recordOutputs?: boolean;
312
+ /**
313
+ * Emit an `OutputScope` at turn completion in addition to recording the
314
+ * response on the invoke scope. Defaults to true.
315
+ */
316
+ emitOutputScope?: boolean;
317
+ };
251
318
  type MicrosoftA365ActivityAccountLike = {
252
319
  id?: string;
253
320
  name?: string;
@@ -377,6 +444,9 @@ declare const MICROSOFT_A365_ATTRIBUTES: {
377
444
 
378
445
  declare function buildMicrosoftA365BaggagePairs(context: MicrosoftA365RequestContext): Record<string, string>;
379
446
 
447
+ declare function currentMicrosoftA365RequestContext(): MicrosoftA365RequestContext | undefined;
448
+ declare function runWithMicrosoftA365RequestContext<T>(requestContext: MicrosoftA365RequestContext, fn: () => T): T;
449
+
380
450
  /**
381
451
  * Registers Microsoft's A365 hosting middleware on a TurnContext-style adapter.
382
452
  *
@@ -386,6 +456,11 @@ declare function buildMicrosoftA365BaggagePairs(context: MicrosoftA365RequestCon
386
456
  */
387
457
  declare function configureMicrosoftA365Hosting(adapter: MicrosoftA365HostingAdapterLike, options?: MicrosoftA365HostingConfigurationOptions): Promise<unknown>;
388
458
 
459
+ type MicrosoftA365ScopeMiddlewareConfig = MicrosoftA365ScopeMiddlewareOptions & {
460
+ runtime?: MicrosoftOpenTelemetryRuntimeOptions;
461
+ };
462
+ declare function createMicrosoftA365ScopeMiddleware(options?: MicrosoftA365ScopeMiddlewareConfig): AgentMiddleware;
463
+
389
464
  declare function createMicrosoftA365ContextFromTurnContext(turnContext: MicrosoftA365TurnContextLike, options?: MicrosoftA365TurnContextOptions): MicrosoftA365RequestContext;
390
465
 
391
466
  type MicrosoftA365ObservedTurnSourceContextInput = {
@@ -437,4 +512,4 @@ declare function loadOpenTelemetryResourceModule(options?: MicrosoftOpenTelemetr
437
512
 
438
513
  declare function createMicrosoftOpenTelemetryTracingConfig(options?: MicrosoftOpenTelemetryTracingConfigOptions): MicrosoftOpenTelemetryTracingConfig;
439
514
 
440
- export { MICROSOFT_A365_ATTRIBUTES, MICROSOFT_A365_FMI_SCOPE, MICROSOFT_A365_OBSERVABILITY_SCOPE, type MicrosoftA365ActivityAccountLike, type MicrosoftA365ActivityLike, type MicrosoftA365HostingAdapterLike, type MicrosoftA365HostingConfigurationOptions, type MicrosoftA365HostingOptions, type MicrosoftA365ObservedTurnSourceContextFactory, type MicrosoftA365ObservedTurnSourceContextInput, type MicrosoftA365ObservedTurnSourceOptions, type MicrosoftA365RequestContext, type MicrosoftA365S2STokenResolver, type MicrosoftA365S2STokenResolverFromEnvOptions, type MicrosoftA365S2STokenResolverLogger, type MicrosoftA365S2STokenResolverOptions, type MicrosoftA365TurnContextLike, type MicrosoftA365TurnContextOptions, type MicrosoftBaggageBuilderLike, type MicrosoftLangChainInstrumentationOptions, type MicrosoftManagedIdentityAssertionProvider, type MicrosoftOpenAIAgentsInstrumentationOptions, type MicrosoftOpenTelemetryA365Options, type MicrosoftOpenTelemetryAzureMonitorOptions, type MicrosoftOpenTelemetryDestinationSummary, type MicrosoftOpenTelemetryDistroModule, type MicrosoftOpenTelemetryEnvironment, type MicrosoftOpenTelemetryHandle, type MicrosoftOpenTelemetryInstrumentationConfig, type MicrosoftOpenTelemetryInstrumentationOptions, type MicrosoftOpenTelemetryInstrumentationProfile, type MicrosoftOpenTelemetryLogger, MicrosoftOpenTelemetryModuleLoadError, type MicrosoftOpenTelemetryOptions, type MicrosoftOpenTelemetryResolvedEnvironment, type MicrosoftOpenTelemetryResourceModule, MicrosoftOpenTelemetryResourceModuleLoadError, type MicrosoftOpenTelemetryResourceOptions, type MicrosoftOpenTelemetryRuntimeOptions, type MicrosoftOpenTelemetryTokenResolver, type MicrosoftOpenTelemetryTracingConfig, type MicrosoftOpenTelemetryTracingConfigOptions, type SpanAttributeValue, buildMicrosoftA365BaggagePairs, configureMicrosoftA365Hosting, createAzureMonitorOptionsFromEnv, createEnvReader, createMicrosoftA365ContextFromTurnContext, createMicrosoftA365ObservedTurnSource, createMicrosoftA365S2STokenResolver, createMicrosoftA365S2STokenResolverFromEnv, createMicrosoftOpenTelemetryInstrumentationOptions, createMicrosoftOpenTelemetryTracingConfig, initMicrosoftOpenTelemetry, initMicrosoftOpenTelemetryFromEnv, isAzureMonitorEnvironmentEnabled, isMicrosoftOtlpEnvironmentEnabled, loadMicrosoftOpenTelemetryModule, loadOpenTelemetryResourceModule, mergeInstrumentationOptions, normalizeInstrumentationProfile, resolveMicrosoftOpenTelemetryBooleanEnv, resolveMicrosoftOpenTelemetryEnvironment, resolveMicrosoftOpenTelemetryNumberEnv, runWithMicrosoftA365Context, summarizeMicrosoftOpenTelemetryDestinations, updateMicrosoftA365ExportToken };
515
+ export { MICROSOFT_A365_ATTRIBUTES, MICROSOFT_A365_FMI_SCOPE, MICROSOFT_A365_OBSERVABILITY_SCOPE, type MicrosoftA365ActivityAccountLike, type MicrosoftA365ActivityLike, type MicrosoftA365ExecuteToolScopeConstructor, type MicrosoftA365HostingAdapterLike, type MicrosoftA365HostingConfigurationOptions, type MicrosoftA365HostingOptions, type MicrosoftA365InferenceScopeConstructor, type MicrosoftA365InvokeAgentScopeConstructor, type MicrosoftA365ObservedTurnSourceContextFactory, type MicrosoftA365ObservedTurnSourceContextInput, type MicrosoftA365ObservedTurnSourceOptions, type MicrosoftA365OutputScopeConstructor, type MicrosoftA365ParentSpanRef, type MicrosoftA365RequestContext, type MicrosoftA365S2STokenResolver, type MicrosoftA365S2STokenResolverFromEnvOptions, type MicrosoftA365S2STokenResolverLogger, type MicrosoftA365S2STokenResolverOptions, type MicrosoftA365ScopeLike, type MicrosoftA365ScopeMiddlewareConfig, type MicrosoftA365ScopeMiddlewareOptions, type MicrosoftA365TurnContextLike, type MicrosoftA365TurnContextOptions, type MicrosoftBaggageBuilderLike, type MicrosoftLangChainInstrumentationOptions, type MicrosoftManagedIdentityAssertionProvider, type MicrosoftOpenAIAgentsInstrumentationOptions, type MicrosoftOpenTelemetryA365Options, type MicrosoftOpenTelemetryAzureMonitorOptions, type MicrosoftOpenTelemetryDestinationSummary, type MicrosoftOpenTelemetryDistroModule, type MicrosoftOpenTelemetryEnvironment, type MicrosoftOpenTelemetryHandle, type MicrosoftOpenTelemetryInstrumentationConfig, type MicrosoftOpenTelemetryInstrumentationOptions, type MicrosoftOpenTelemetryInstrumentationProfile, type MicrosoftOpenTelemetryLogger, MicrosoftOpenTelemetryModuleLoadError, type MicrosoftOpenTelemetryOptions, type MicrosoftOpenTelemetryResolvedEnvironment, type MicrosoftOpenTelemetryResourceModule, MicrosoftOpenTelemetryResourceModuleLoadError, type MicrosoftOpenTelemetryResourceOptions, type MicrosoftOpenTelemetryRuntimeOptions, type MicrosoftOpenTelemetryTokenResolver, type MicrosoftOpenTelemetryTracingConfig, type MicrosoftOpenTelemetryTracingConfigOptions, type SpanAttributeValue, buildMicrosoftA365BaggagePairs, configureMicrosoftA365Hosting, createAzureMonitorOptionsFromEnv, createEnvReader, createMicrosoftA365ContextFromTurnContext, createMicrosoftA365ObservedTurnSource, createMicrosoftA365S2STokenResolver, createMicrosoftA365S2STokenResolverFromEnv, createMicrosoftA365ScopeMiddleware, createMicrosoftOpenTelemetryInstrumentationOptions, createMicrosoftOpenTelemetryTracingConfig, currentMicrosoftA365RequestContext, initMicrosoftOpenTelemetry, initMicrosoftOpenTelemetryFromEnv, isAzureMonitorEnvironmentEnabled, isMicrosoftOtlpEnvironmentEnabled, loadMicrosoftOpenTelemetryModule, loadOpenTelemetryResourceModule, mergeInstrumentationOptions, normalizeInstrumentationProfile, resolveMicrosoftOpenTelemetryBooleanEnv, resolveMicrosoftOpenTelemetryEnvironment, resolveMicrosoftOpenTelemetryNumberEnv, runWithMicrosoftA365Context, runWithMicrosoftA365RequestContext, summarizeMicrosoftOpenTelemetryDestinations, updateMicrosoftA365ExportToken };
package/dist/index.js CHANGED
@@ -10,8 +10,10 @@ import {
10
10
  createEnvReader,
11
11
  createMicrosoftA365S2STokenResolver,
12
12
  createMicrosoftA365S2STokenResolverFromEnv,
13
+ createMicrosoftA365ScopeMiddleware,
13
14
  createMicrosoftOpenTelemetryInstrumentationOptions,
14
15
  createMicrosoftOpenTelemetryTracingConfig,
16
+ currentMicrosoftA365RequestContext,
15
17
  firstNonEmpty,
16
18
  initMicrosoftOpenTelemetry,
17
19
  initMicrosoftOpenTelemetryFromEnv,
@@ -25,8 +27,9 @@ import {
25
27
  resolveMicrosoftOpenTelemetryEnvironment,
26
28
  resolveMicrosoftOpenTelemetryNumberEnv,
27
29
  runWithMicrosoftA365Context,
30
+ runWithMicrosoftA365RequestContext,
28
31
  updateMicrosoftA365ExportToken
29
- } from "./chunk-S7XVXBY3.js";
32
+ } from "./chunk-K54CB7U4.js";
30
33
 
31
34
  // src/a365/hosting.ts
32
35
  async function configureMicrosoftA365Hosting(adapter, options = {}) {
@@ -239,10 +242,15 @@ function createMicrosoftA365ObservedTurnSource({
239
242
  message,
240
243
  options
241
244
  });
245
+ const scopedRequestContext = {
246
+ ...requestContext,
247
+ sessionId: requestContext.sessionId ?? sessionId,
248
+ conversationId: requestContext.conversationId ?? sessionId
249
+ };
242
250
  const queue = new AsyncQueue();
243
251
  let failure;
244
252
  void runWithMicrosoftA365Context(
245
- requestContext,
253
+ scopedRequestContext,
246
254
  async () => {
247
255
  try {
248
256
  for await (const event of source.chat(
@@ -344,8 +352,10 @@ export {
344
352
  createMicrosoftA365ObservedTurnSource,
345
353
  createMicrosoftA365S2STokenResolver,
346
354
  createMicrosoftA365S2STokenResolverFromEnv,
355
+ createMicrosoftA365ScopeMiddleware,
347
356
  createMicrosoftOpenTelemetryInstrumentationOptions,
348
357
  createMicrosoftOpenTelemetryTracingConfig,
358
+ currentMicrosoftA365RequestContext,
349
359
  initMicrosoftOpenTelemetry,
350
360
  initMicrosoftOpenTelemetryFromEnv,
351
361
  isAzureMonitorEnvironmentEnabled,
@@ -358,6 +368,7 @@ export {
358
368
  resolveMicrosoftOpenTelemetryEnvironment,
359
369
  resolveMicrosoftOpenTelemetryNumberEnv,
360
370
  runWithMicrosoftA365Context,
371
+ runWithMicrosoftA365RequestContext,
361
372
  summarizeMicrosoftOpenTelemetryDestinations,
362
373
  updateMicrosoftA365ExportToken
363
374
  };
package/dist/register.js CHANGED
@@ -1,6 +1,6 @@
1
1
  import {
2
2
  initMicrosoftOpenTelemetryFromEnv
3
- } from "./chunk-S7XVXBY3.js";
3
+ } from "./chunk-K54CB7U4.js";
4
4
 
5
5
  // src/register.ts
6
6
  var DISABLED_VALUES = /* @__PURE__ */ new Set(["1", "true", "yes", "on"]);
@@ -7,8 +7,8 @@ adapter for `agents-ts`.
7
7
 
8
8
  The package keeps three boundaries separate.
9
9
 
10
- First, `@cuylabs/agent-core` remains the owner of agent execution telemetry. It
11
- configures AI SDK v7 GenAI telemetry for model calls and tool execution and
10
+ First, `@cuylabs/agent-core` remains the owner of agent execution. It runs the
11
+ turn loop, model calls, tool execution, approvals, and event stream, and it
12
12
  provides `createAgent({ tracing })` as the app-facing integration point.
13
13
 
14
14
  Second, `@microsoft/opentelemetry` owns the Microsoft OpenTelemetry provider,
@@ -17,8 +17,8 @@ export, and Microsoft-supported framework instrumentation.
17
17
 
18
18
  Third, this package bridges the two. It starts the Microsoft distro, creates
19
19
  Agent 365 S2S token resolvers, applies Agent 365 baggage around turns, and
20
- returns `agent-core` tracing defaults that align with Microsoft Agent 365
21
- schemas.
20
+ returns `agent-core` tracing defaults that install official A365 scope
21
+ middleware.
22
22
 
23
23
  The adapter default instrumentation profile is `agent-core`. It disables
24
24
  Microsoft's optional framework auto-instrumentations for OpenAI Agents SDK and
@@ -33,14 +33,14 @@ A365 processors/exporters/scopes, Azure Monitor, OTLP, GenAI instrumentation,
33
33
  and shared distro setup. This package does not copy those internals. It keeps
34
34
  only the adapter responsibilities:
35
35
 
36
- | Folder | Responsibility |
37
- | --- | --- |
38
- | `src/a365` | Normalize channel/runtime data into Microsoft A365 baggage. |
39
- | `src/auth` | Resolve Agent 365 S2S tokens from CLI-generated configuration or host-provided managed identity assertions. |
40
- | `src/runtime` | Call `useMicrosoftOpenTelemetry()`, map adapter options to distro options, expose destination helpers, expose instrumentation profiles, and load Microsoft modules lazily. |
41
- | `src/tracing` | Produce `agent-core` tracing config that avoids duplicate tool spans and carries Microsoft attributes. |
42
- | `src/common` | Public cross-cutting type aliases. |
43
- | `src/internal` | Small helpers that are not part of the package API. |
36
+ | Folder | Responsibility |
37
+ | -------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
38
+ | `src/a365` | Normalize channel/runtime data into Microsoft A365 baggage and create official A365 scopes from agent-core lifecycle hooks. |
39
+ | `src/auth` | Resolve Agent 365 S2S tokens from CLI-generated configuration or host-provided managed identity assertions. |
40
+ | `src/runtime` | Call `useMicrosoftOpenTelemetry()`, map adapter options to distro options, expose destination helpers, expose instrumentation profiles, and load Microsoft modules lazily. |
41
+ | `src/tracing` | Produce `agent-core` tracing config that avoids duplicate tool spans and carries Microsoft attributes. |
42
+ | `src/common` | Public cross-cutting type aliases. |
43
+ | `src/internal` | Small helpers that are not part of the package API. |
44
44
 
45
45
  ## Agent 365 Flow
46
46
 
@@ -62,6 +62,22 @@ observed turn source. That scope creates Microsoft baggage values such as
62
62
  `microsoft.channel.name`, and `gen_ai.conversation.id`. The Microsoft distro
63
63
  copies those baggage values onto spans before export.
64
64
 
65
+ The tracing config returned by this package installs a scope middleware that
66
+ maps agent-core lifecycle hooks to Microsoft's official A365 scope classes:
67
+
68
+ ```text
69
+ AgentTurnSource.chat()
70
+ └─ runWithMicrosoftA365Context()
71
+ ├─ InvokeAgentScope turn start/end
72
+ ├─ InferenceScope each model step
73
+ ├─ ExecuteToolScope tool-start/tool-result/tool-error events
74
+ └─ OutputScope final turn output
75
+ ```
76
+
77
+ The middleware also exposes the active A365 parent context back to agent-core so
78
+ the actual model call can run under the inference span. This keeps the scope
79
+ tree coherent without asking the application to manually create scopes.
80
+
65
81
  For Microsoft Agent Hosting compatible adapters, the package can also delegate
66
82
  to the official distro's hosting middleware with
67
83
  `configureMicrosoftA365Hosting()`. This keeps Microsoft TurnContext middleware
@@ -74,16 +90,20 @@ the Agent 365 FMI flow and caches the resulting Observability API token.
74
90
 
75
91
  ## Tool Spans
76
92
 
77
- The default tracing helper returns `emitToolSpans: false`. This is intentional.
78
- AI SDK v7 GenAI telemetry already emits standard tool spans for normal
79
- `streamText()` tool execution. Enabling both AI SDK tool spans and agent-core
80
- custom tool spans creates duplicate `execute_tool` telemetry for one tool call.
93
+ The default tracing helper returns `useA365Scopes: true`,
94
+ `useDefaultOtelMiddleware: false`, `useGenAIOpenTelemetry: false`, and
95
+ `emitToolSpans: false`. This is intentional. The official A365 scope middleware
96
+ now owns `ExecuteToolScope` creation for `agents-ts` turns. Enabling the older
97
+ generic middleware or local AI SDK GenAI telemetry at the same time can create
98
+ duplicate `execute_tool` records.
81
99
 
82
- If an application has a custom execution path that does not emit AI SDK tool
83
- spans, it can opt in with:
100
+ If an application has a custom execution path that should bypass the official
101
+ A365 scope middleware, it can opt out and use generic agent-core OTel spans:
84
102
 
85
103
  ```ts
86
104
  createMicrosoftOpenTelemetryTracingConfig({
105
+ useA365Scopes: false,
106
+ useDefaultOtelMiddleware: true,
87
107
  emitToolSpans: true,
88
108
  });
89
109
  ```
@@ -91,7 +111,7 @@ createMicrosoftOpenTelemetryTracingConfig({
91
111
  ## Content Capture
92
112
 
93
113
  The package does not force a privacy policy. `recordInputs` and `recordOutputs`
94
- are explicit `agent-core` tracing settings. Set them to `true` when Agent 365
114
+ control what the official A365 scopes record. Set them to `true` when Agent 365
95
115
  validation or audit scenarios require prompt, response, tool argument, and tool
96
116
  result attributes. Set them to `false` when an application needs metadata-only
97
117
  telemetry.
@@ -7,12 +7,12 @@ telemetry is created.
7
7
 
8
8
  Destinations are exporters attached to the same OpenTelemetry pipeline.
9
9
 
10
- | Destination | Purpose | How to enable |
11
- | --- | --- | --- |
12
- | Agent 365 | Microsoft 365 admin, Defender, and Purview agent activity. | `a365.enabled: true` plus `a365.enableObservabilityExporter: true`. |
13
- | Azure Monitor | Application operations, traces, dependencies, logs, metrics, dashboards, and alerts. | `azureMonitor: { enabled: true }` or `APPLICATIONINSIGHTS_CONNECTION_STRING`. |
14
- | OTLP | Vendor-neutral export to collectors or backends such as Grafana, Datadog, or New Relic. | Standard `OTEL_EXPORTER_OTLP_*` environment variables. |
15
- | Console | Local validation when no remote exporter is active, or explicit debugging. | `enableConsoleExporters: true`. |
10
+ | Destination | Purpose | How to enable |
11
+ | ------------- | --------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------- |
12
+ | Agent 365 | Microsoft 365 admin, Defender, and Purview agent activity. | `a365.enabled: true` plus `a365.enableObservabilityExporter: true`. |
13
+ | Azure Monitor | Application operations, traces, dependencies, logs, metrics, dashboards, and alerts. | `azureMonitor: { enabled: true }` or `APPLICATIONINSIGHTS_CONNECTION_STRING`. |
14
+ | OTLP | Vendor-neutral export to collectors or backends such as Grafana, Datadog, or New Relic. | Standard `OTEL_EXPORTER_OTLP_*` environment variables. |
15
+ | Console | Local validation when no remote exporter is active, or explicit debugging. | `enableConsoleExporters: true`. |
16
16
 
17
17
  The same spans can flow to multiple destinations. Agent 365 enrichment still
18
18
  helps Azure Monitor and OTLP when `a365.enabled` is true because the Microsoft
@@ -22,12 +22,12 @@ distro's `A365SpanProcessor` copies baggage onto spans before export.
22
22
 
23
23
  Instrumentation controls which libraries create spans.
24
24
 
25
- | Instrumentation | Purpose |
26
- | --- | --- |
27
- | `http`, `azureSdk`, databases, Redis, Bunyan, Winston | Infrastructure and application telemetry. |
28
- | `openaiAgents` | Optional Microsoft auto-instrumentation for the OpenAI Agents SDK when `@openai/agents` is installed. |
29
- | `langchain` | Optional Microsoft auto-instrumentation for LangChain when `@langchain/core` is installed. |
30
- | `agent-core` tracing | Emits `agents-ts` agent invocation spans and passes AI SDK v7 GenAI telemetry into `streamText()`. |
25
+ | Instrumentation | Purpose |
26
+ | ----------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------- |
27
+ | `http`, `azureSdk`, databases, Redis, Bunyan, Winston | Infrastructure and application telemetry. |
28
+ | `openaiAgents` | Optional Microsoft auto-instrumentation for the OpenAI Agents SDK when `@openai/agents` is installed. |
29
+ | `langchain` | Optional Microsoft auto-instrumentation for LangChain when `@langchain/core` is installed. |
30
+ | A365 scope middleware | Converts `agents-ts` turns into official `InvokeAgentScope`, `InferenceScope`, `ExecuteToolScope`, and `OutputScope` spans. |
31
31
 
32
32
  `@cuylabs/agent-core` does not use OpenAI Agents SDK or LangChain internally.
33
33
  Those Microsoft framework instrumentations are useful only when an application
@@ -43,13 +43,13 @@ await initMicrosoftOpenTelemetry({
43
43
  });
44
44
  ```
45
45
 
46
- | Profile | Behavior |
47
- | --- | --- |
48
- | `agent-core` | Disables Microsoft infra and framework auto-instrumentation. Best default for `agents-ts`, where `agent-core` emits the agent/model/tool telemetry. |
49
- | `microsoft-genai` | Disables infrastructure auto-instrumentation and enables Microsoft's OpenAI Agents SDK and LangChain auto-instrumentations. |
50
- | `full-stack` | Enables infrastructure and Microsoft framework auto-instrumentation. Best when sending to Azure Monitor or a general OTLP backend too. |
51
- | `manual` | Disables built-in auto-instrumentation. Best when the app supplies its own processors/instrumentation. |
52
- | `microsoft-default` | Leaves `instrumentationOptions` unset so the official distro applies its native defaults. |
46
+ | Profile | Behavior |
47
+ | ------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
48
+ | `agent-core` | Disables Microsoft infra and optional framework auto-instrumentation. Best default for `agents-ts`, where the adapter's A365 scope middleware emits the agent/model/tool telemetry. |
49
+ | `microsoft-genai` | Disables infrastructure auto-instrumentation and enables Microsoft's OpenAI Agents SDK and LangChain auto-instrumentations. |
50
+ | `full-stack` | Enables infrastructure and Microsoft framework auto-instrumentation. Best when sending to Azure Monitor or a general OTLP backend too. |
51
+ | `manual` | Disables built-in auto-instrumentation. Best when the app supplies its own processors/instrumentation. |
52
+ | `microsoft-default` | Leaves `instrumentationOptions` unset so the official distro applies its native defaults. |
53
53
 
54
54
  Explicit `instrumentationOptions` always override the selected profile.
55
55
  The old pre-release name `agent365-genai` is still accepted as an alias for
@@ -60,9 +60,11 @@ The old pre-release name `agent365-genai` is still accepted as an alias for
60
60
  There are two content-recording surfaces only if the application uses both
61
61
  `agent-core` and Microsoft's optional framework auto-instrumentations.
62
62
 
63
- `agent-core` uses `agent.recordInputs` and `agent.recordOutputs` in this
64
- adapter's tracing config. Those settings control AI SDK v7 GenAI telemetry
65
- created by `@cuylabs/agent-core`.
63
+ For normal `agents-ts` turns, use `agent.recordInputs` and
64
+ `agent.recordOutputs` in this adapter's tracing config. Those settings control
65
+ content recording on the official A365 scopes created by the adapter:
66
+ invocation input/output, inference input/output, tool arguments/results, and
67
+ final output.
66
68
 
67
69
  Microsoft's optional OpenAI Agents and LangChain auto-instrumentations use their
68
70
  own official flags:
@@ -83,7 +85,7 @@ await initMicrosoftOpenTelemetry({
83
85
  ```
84
86
 
85
87
  Set these only when the process actually uses those frameworks. They do not
86
- control `agent-core`'s AI SDK spans.
88
+ control the A365 scopes created for `@cuylabs/agent-core`.
87
89
 
88
90
  ## Example: Agent 365 and Azure Monitor
89
91
 
@@ -24,17 +24,17 @@ Apps that initialize observability in code can call
24
24
 
25
25
  `initMicrosoftOpenTelemetry()` maps directly to `useMicrosoftOpenTelemetry()`.
26
26
 
27
- | Microsoft distro option | Adapter option |
28
- | --- | --- |
29
- | `resource` | `resource` with `serviceName`, `serviceVersion`, and custom attributes. |
30
- | `azureMonitor` | `azureMonitor`. |
31
- | `instrumentationOptions` | `instrumentationOptions`. |
32
- | `agent-core`-focused instrumentation defaults | `instrumentationProfile: "agent-core"`. |
33
- | `a365.enabled` | `a365.enabled`. |
34
- | `a365.enableObservabilityExporter` | `a365.enableObservabilityExporter`. |
35
- | `a365.tokenResolver` | `a365.tokenResolver`. |
36
- | `a365.useS2SEndpoint` | `a365.useS2SEndpoint`. |
37
- | Export queue, timeout, batch, cluster, domain, and scope options | Same A365 option names. |
27
+ | Microsoft distro option | Adapter option |
28
+ | ---------------------------------------------------------------- | ----------------------------------------------------------------------- |
29
+ | `resource` | `resource` with `serviceName`, `serviceVersion`, and custom attributes. |
30
+ | `azureMonitor` | `azureMonitor`. |
31
+ | `instrumentationOptions` | `instrumentationOptions`. |
32
+ | `agent-core`-focused instrumentation defaults | `instrumentationProfile: "agent-core"`. |
33
+ | `a365.enabled` | `a365.enabled`. |
34
+ | `a365.enableObservabilityExporter` | `a365.enableObservabilityExporter`. |
35
+ | `a365.tokenResolver` | `a365.tokenResolver`. |
36
+ | `a365.useS2SEndpoint` | `a365.useS2SEndpoint`. |
37
+ | Export queue, timeout, batch, cluster, domain, and scope options | Same A365 option names. |
38
38
 
39
39
  The adapter also accepts `a365.exporterEnabled` as a compatibility alias for
40
40
  `a365.enableObservabilityExporter`. New code should use Microsoft's official
@@ -80,10 +80,11 @@ For apps that do use Microsoft Agent Hosting compatible adapters, call
80
80
  to the official distro's `configureA365Hosting()` helper, with a fallback for
81
81
  older distro versions that expose `ObservabilityHostingManager` instead.
82
82
 
83
- ## Agent-Core Tracing
83
+ ## Official A365 Scope Model
84
84
 
85
85
  The adapter does not create a second agent runtime. It returns tracing settings
86
- for `createAgent({ tracing })`:
86
+ for `createAgent({ tracing })` that install an `AgentMiddleware` backed by the
87
+ official Microsoft A365 scope classes:
87
88
 
88
89
  ```ts
89
90
  const microsoftOtel = await initMicrosoftOpenTelemetryFromEnv();
@@ -95,10 +96,34 @@ const agent = createAgent({
95
96
  });
96
97
  ```
97
98
 
98
- `agent-core` emits the agent invocation span and passes AI SDK v7 GenAI telemetry
99
- into `streamText()`. The Microsoft distro owns the provider, span processors,
100
- and exporters. This keeps one OpenTelemetry pipeline while avoiding duplicate
101
- tool spans.
99
+ The middleware maps one `agents-ts` turn into A365 scopes:
100
+
101
+ | agent-core signal | Microsoft scope |
102
+ | ------------------------------------------------ | ------------------ |
103
+ | chat start/end | `InvokeAgentScope` |
104
+ | model step start/output | `InferenceScope` |
105
+ | `tool-start`, `tool-result`, `tool-error` events | `ExecuteToolScope` |
106
+ | final turn output | `OutputScope` |
107
+
108
+ It also returns the active A365 parent context to agent-core so the underlying
109
+ model call is executed under the inference span. The Microsoft distro owns the
110
+ provider, span processors, and exporters. This keeps one OpenTelemetry pipeline
111
+ while avoiding duplicate generic tool spans.
112
+
113
+ The default tracing values are:
114
+
115
+ ```ts
116
+ {
117
+ useA365Scopes: true,
118
+ useDefaultOtelMiddleware: false,
119
+ useGenAIOpenTelemetry: false,
120
+ emitToolSpans: false,
121
+ }
122
+ ```
123
+
124
+ Use `useA365Scopes: false` only when an application intentionally wants the
125
+ older generic agent-core OTel lifecycle instead of Microsoft's A365 product
126
+ scope model.
102
127
 
103
128
  ## Optional Microsoft Framework Instrumentation
104
129
 
@@ -113,9 +138,9 @@ frameworks. The adapter's default `agent-core` profile disables them because the
113
138
  normal `agents-ts` path does not execute through those frameworks.
114
139
 
115
140
  The adapter types expose Microsoft's `isContentRecordingEnabled` flags for
116
- those framework instrumentations. They are separate from `agent-core`
117
- `recordInputs` and `recordOutputs`, which control AI SDK v7 telemetry produced
118
- through `@cuylabs/agent-core`.
141
+ those framework instrumentations. They are separate from the adapter's
142
+ `recordInputs` and `recordOutputs`, which control content recording on the
143
+ official A365 scopes created for `@cuylabs/agent-core`.
119
144
 
120
145
  The early pre-release profile name `agent365-genai` remains accepted as a
121
146
  compatibility alias for `microsoft-genai`, but new code should use
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@cuylabs/agent-microsoft-opentelemetry",
3
- "version": "4.8.0",
3
+ "version": "4.8.1",
4
4
  "description": "Microsoft OpenTelemetry distro adapter for @cuylabs/agent-core",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",
@@ -30,6 +30,7 @@
30
30
  ],
31
31
  "devDependencies": {
32
32
  "@ai-sdk/openai": "4.0.0-beta.38",
33
+ "@opentelemetry/api": "^1.9.0",
33
34
  "@types/node": "^22.0.0",
34
35
  "ai": "7.0.0-beta.111",
35
36
  "dotenv": "^17.2.3",
@@ -37,11 +38,12 @@
37
38
  "typescript": "^5.7.0",
38
39
  "vitest": "^4.0.18",
39
40
  "zod": "^3.25.76 || ^4.1.8",
40
- "@cuylabs/agent-core": "^4.8.0"
41
+ "@cuylabs/agent-core": "^4.8.1"
41
42
  },
42
43
  "peerDependencies": {
43
44
  "@cuylabs/agent-core": "^4.0.0",
44
45
  "@microsoft/opentelemetry": ">=1.0.1 <2.0.0",
46
+ "@opentelemetry/api": "^1.9.0",
45
47
  "ai": "7.0.0-beta.111"
46
48
  },
47
49
  "keywords": [