@cuylabs/agent-foundry-agentserver-core 4.1.0 → 4.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -8,3 +8,26 @@ sanitization, request correlation IDs, server-version header formatting, and
8
8
  standard error envelopes. Protocol packages such as
9
9
  `@cuylabs/agent-foundry-agentserver-invocations` build their HTTP surface on top
10
10
  of these helpers.
11
+
12
+ ## Observability
13
+
14
+ Core always creates OpenTelemetry-compatible request spans and installs the
15
+ Node async context manager so protocol handlers can read active baggage. It
16
+ sets the Foundry baggage keys used by the Python host:
17
+
18
+ ```text
19
+ azure.ai.agentserver.invocation_id
20
+ azure.ai.agentserver.session_id
21
+ ```
22
+
23
+ Exporter setup is optional and is driven by the same environment variables used
24
+ by hosted agents:
25
+
26
+ | Variable | Behavior |
27
+ | --- | --- |
28
+ | `APPLICATIONINSIGHTS_CONNECTION_STRING` | Enables Azure Monitor export through the package's optional `@azure/monitor-opentelemetry` dependency |
29
+ | `OTEL_EXPORTER_OTLP_ENDPOINT` | Enables OTLP HTTP trace export through the package's optional OpenTelemetry exporter dependencies |
30
+
31
+ Protocol packages call `configureAgentServerObservability()` by default. Apps
32
+ that already own OpenTelemetry setup can disable package-managed exporter setup
33
+ at the protocol layer and keep the span/baggage helpers.
package/dist/index.d.ts CHANGED
@@ -1,3 +1,5 @@
1
+ import { Span, Context } from '@opentelemetry/api';
2
+
1
3
  interface AgentServerConfig {
2
4
  agentName: string;
3
5
  agentVersion: string;
@@ -10,15 +12,23 @@ interface AgentServerConfig {
10
12
  applicationInsightsConnectionString: string;
11
13
  otlpEndpoint: string;
12
14
  sseKeepAliveIntervalSeconds: number;
15
+ gracefulShutdownTimeoutSeconds: number;
16
+ logLevel: AgentServerLogLevel;
13
17
  }
14
18
  interface AgentServerConfigOptions {
15
19
  env?: NodeJS.ProcessEnv;
16
20
  port?: number;
17
21
  sseKeepAliveIntervalSeconds?: number;
22
+ gracefulShutdownTimeoutSeconds?: number;
23
+ logLevel?: string;
18
24
  }
25
+ declare const LOG_LEVELS: readonly ["DEBUG", "INFO", "WARNING", "ERROR", "CRITICAL"];
26
+ type AgentServerLogLevel = (typeof LOG_LEVELS)[number];
19
27
  declare function resolveAgentServerConfig(options?: AgentServerConfigOptions): AgentServerConfig;
20
28
  declare function resolvePort(explicitPort?: number, env?: NodeJS.ProcessEnv): number;
21
29
  declare function resolveSseKeepAliveInterval(explicitInterval?: number, env?: NodeJS.ProcessEnv): number;
30
+ declare function resolveGracefulShutdownTimeout(explicitTimeout?: number): number;
31
+ declare function resolveLogLevel(explicitLevel?: string): AgentServerLogLevel;
22
32
 
23
33
  interface AgentServerErrorBody {
24
34
  error: {
@@ -60,6 +70,22 @@ declare function resolveInvocationIdentity(input: InvocationIdentityInput): Invo
60
70
  declare function resolveRequestId(header: HeaderGetter): string;
61
71
  declare function sanitizeProtocolId(value: string | undefined, fallback: string): string;
62
72
 
73
+ interface AgentServerObservabilityLogger {
74
+ info(message: string, meta?: Record<string, unknown>): void;
75
+ warn(message: string, meta?: Record<string, unknown>): void;
76
+ debug?(message: string, meta?: Record<string, unknown>): void;
77
+ }
78
+ interface AgentServerObservabilityHandle {
79
+ configured: boolean;
80
+ exporters: string[];
81
+ shutdown(): Promise<void>;
82
+ }
83
+ interface ConfigureAgentServerObservabilityOptions {
84
+ config: AgentServerConfig;
85
+ logger?: AgentServerObservabilityLogger;
86
+ }
87
+ declare function configureAgentServerObservability(options: ConfigureAgentServerObservabilityOptions): Promise<AgentServerObservabilityHandle>;
88
+
63
89
  interface ServerVersionSegment {
64
90
  name: string;
65
91
  version: string;
@@ -69,4 +95,55 @@ declare function buildPlatformServerHeader(segments: readonly ServerVersionSegme
69
95
 
70
96
  declare const AGENTSERVER_CORE_PACKAGE_VERSION: string;
71
97
 
72
- export { AGENTSERVER_CORE_PACKAGE_VERSION, type AgentServerConfig, type AgentServerConfigOptions, type AgentServerErrorBody, CHAT_ISOLATION_HEADER, type HeaderGetter, INVOCATION_ID_HEADER, INVOCATION_ID_HEADERS, type InvocationIdentity, type InvocationIdentityInput, type QueryGetter, REQUEST_ID_HEADER, SESSION_ID_HEADER, SESSION_ID_HEADERS, USER_ISOLATION_HEADER, buildPlatformServerHeader, buildServerVersionSegment, createErrorBody, resolveAgentServerConfig, resolveInvocationIdentity, resolvePort, resolveRequestId, resolveSseKeepAliveInterval, sanitizeProtocolId };
98
+ declare const AGENTSERVER_INVOCATION_ID_BAGGAGE = "azure.ai.agentserver.invocation_id";
99
+ declare const AGENTSERVER_SESSION_ID_BAGGAGE = "azure.ai.agentserver.session_id";
100
+ declare const AGENTSERVER_CONVERSATION_ID_BAGGAGE = "azure.ai.agentserver.conversation_id";
101
+ declare const ATTR_SERVICE_NAME = "service.name";
102
+ declare const ATTR_GEN_AI_SYSTEM = "gen_ai.system";
103
+ declare const ATTR_GEN_AI_PROVIDER_NAME = "gen_ai.provider.name";
104
+ declare const ATTR_GEN_AI_AGENT_ID = "gen_ai.agent.id";
105
+ declare const ATTR_GEN_AI_AGENT_NAME = "gen_ai.agent.name";
106
+ declare const ATTR_GEN_AI_AGENT_VERSION = "gen_ai.agent.version";
107
+ declare const ATTR_GEN_AI_RESPONSE_ID = "gen_ai.response.id";
108
+ declare const ATTR_GEN_AI_OPERATION_NAME = "gen_ai.operation.name";
109
+ declare const ATTR_GEN_AI_CONVERSATION_ID = "gen_ai.conversation.id";
110
+ declare const ATTR_FOUNDRY_PROJECT_ID = "microsoft.foundry.project.id";
111
+ declare const ATTR_SESSION_ID = "microsoft.session.id";
112
+ declare const ATTR_INVOCATION_ID = "azure.ai.agentserver.invocations.id";
113
+ declare const ATTR_INVOCATIONS_ERROR_CODE = "azure.ai.agentserver.invocations.error.code";
114
+ declare const ATTR_INVOCATIONS_ERROR_MESSAGE = "azure.ai.agentserver.invocations.error.message";
115
+ interface AgentServerRequestSpanOptions {
116
+ config: Pick<AgentServerConfig, "agentId" | "agentName" | "agentVersion" | "projectArmId">;
117
+ headers: HeaderGetter;
118
+ requestId: string;
119
+ operation: string;
120
+ operationName?: string;
121
+ instrumentationScope?: string;
122
+ invocationId?: string;
123
+ sessionId?: string;
124
+ correlationRequestId?: string;
125
+ }
126
+ interface AgentServerRequestSpan {
127
+ readonly span: Span;
128
+ readonly context: Context;
129
+ run<T>(fn: () => T): T;
130
+ end(error?: unknown): void;
131
+ recordError(error: unknown, code?: string): void;
132
+ }
133
+ interface AgentServerSpanProcessor {
134
+ onStart(span: Span, parentContext?: Context): void;
135
+ onEnd(span: unknown): void;
136
+ shutdown(): Promise<void> | void;
137
+ forceFlush(timeoutMillis?: number): Promise<boolean> | boolean;
138
+ }
139
+ declare function startAgentServerRequestSpan(options: AgentServerRequestSpanOptions): AgentServerRequestSpan;
140
+ declare function withAgentServerBaggage(sourceContext: Context, values: {
141
+ invocationId?: string;
142
+ sessionId?: string;
143
+ requestId?: string;
144
+ }): Context;
145
+ declare function recordAgentServerSpanError(span: Span, error: unknown, code?: string): void;
146
+ declare function createFoundryEnrichmentSpanProcessor(config: Pick<AgentServerConfig, "agentId" | "agentName" | "agentVersion" | "projectArmId">): AgentServerSpanProcessor;
147
+ declare function flushSpans(timeoutMillis?: number): void;
148
+
149
+ export { AGENTSERVER_CONVERSATION_ID_BAGGAGE, AGENTSERVER_CORE_PACKAGE_VERSION, AGENTSERVER_INVOCATION_ID_BAGGAGE, AGENTSERVER_SESSION_ID_BAGGAGE, ATTR_FOUNDRY_PROJECT_ID, ATTR_GEN_AI_AGENT_ID, ATTR_GEN_AI_AGENT_NAME, ATTR_GEN_AI_AGENT_VERSION, ATTR_GEN_AI_CONVERSATION_ID, ATTR_GEN_AI_OPERATION_NAME, ATTR_GEN_AI_PROVIDER_NAME, ATTR_GEN_AI_RESPONSE_ID, ATTR_GEN_AI_SYSTEM, ATTR_INVOCATIONS_ERROR_CODE, ATTR_INVOCATIONS_ERROR_MESSAGE, ATTR_INVOCATION_ID, ATTR_SERVICE_NAME, ATTR_SESSION_ID, type AgentServerConfig, type AgentServerConfigOptions, type AgentServerErrorBody, type AgentServerLogLevel, type AgentServerObservabilityHandle, type AgentServerObservabilityLogger, type AgentServerRequestSpan, type AgentServerRequestSpanOptions, type AgentServerSpanProcessor, CHAT_ISOLATION_HEADER, type ConfigureAgentServerObservabilityOptions, type HeaderGetter, INVOCATION_ID_HEADER, INVOCATION_ID_HEADERS, type InvocationIdentity, type InvocationIdentityInput, type QueryGetter, REQUEST_ID_HEADER, SESSION_ID_HEADER, SESSION_ID_HEADERS, USER_ISOLATION_HEADER, buildPlatformServerHeader, buildServerVersionSegment, configureAgentServerObservability, createErrorBody, createFoundryEnrichmentSpanProcessor, flushSpans, recordAgentServerSpanError, resolveAgentServerConfig, resolveGracefulShutdownTimeout, resolveInvocationIdentity, resolveLogLevel, resolvePort, resolveRequestId, resolveSseKeepAliveInterval, sanitizeProtocolId, startAgentServerRequestSpan, withAgentServerBaggage };
package/dist/index.js CHANGED
@@ -1,6 +1,8 @@
1
1
  // src/config.ts
2
2
  var DEFAULT_PORT = 8088;
3
3
  var DEFAULT_SSE_KEEP_ALIVE_INTERVAL = 0;
4
+ var DEFAULT_GRACEFUL_SHUTDOWN_TIMEOUT = 30;
5
+ var LOG_LEVELS = ["DEBUG", "INFO", "WARNING", "ERROR", "CRITICAL"];
4
6
  function resolveAgentServerConfig(options = {}) {
5
7
  const env = options.env ?? process.env;
6
8
  const agentName = env.FOUNDRY_AGENT_NAME ?? "";
@@ -20,7 +22,11 @@ function resolveAgentServerConfig(options = {}) {
20
22
  sseKeepAliveIntervalSeconds: resolveSseKeepAliveInterval(
21
23
  options.sseKeepAliveIntervalSeconds,
22
24
  env
23
- )
25
+ ),
26
+ gracefulShutdownTimeoutSeconds: resolveGracefulShutdownTimeout(
27
+ options.gracefulShutdownTimeoutSeconds
28
+ ),
29
+ logLevel: resolveLogLevel(options.logLevel)
24
30
  };
25
31
  }
26
32
  function resolvePort(explicitPort, env = process.env) {
@@ -56,6 +62,24 @@ function resolveSseKeepAliveInterval(explicitInterval, env = process.env) {
56
62
  }
57
63
  return DEFAULT_SSE_KEEP_ALIVE_INTERVAL;
58
64
  }
65
+ function resolveGracefulShutdownTimeout(explicitTimeout) {
66
+ if (explicitTimeout !== void 0) {
67
+ return Math.max(
68
+ 0,
69
+ validateInteger(explicitTimeout, "gracefulShutdownTimeoutSeconds")
70
+ );
71
+ }
72
+ return DEFAULT_GRACEFUL_SHUTDOWN_TIMEOUT;
73
+ }
74
+ function resolveLogLevel(explicitLevel) {
75
+ const normalized = (explicitLevel ?? "INFO").trim().toUpperCase();
76
+ if (!LOG_LEVELS.includes(normalized)) {
77
+ throw new Error(
78
+ `Invalid value for logLevel: ${explicitLevel} (expected ${LOG_LEVELS.join(", ")})`
79
+ );
80
+ }
81
+ return normalized;
82
+ }
59
83
  function validatePort(value, source) {
60
84
  if (!Number.isInteger(value) || value < 1 || value > 65535) {
61
85
  throw new Error(`Invalid value for ${source}: ${value} (expected 1-65535)`);
@@ -70,6 +94,14 @@ function validateNonNegativeInteger(value, source) {
70
94
  }
71
95
  return value;
72
96
  }
97
+ function validateInteger(value, source) {
98
+ if (!Number.isInteger(value)) {
99
+ throw new Error(
100
+ `Invalid value for ${source}: ${value} (expected an integer)`
101
+ );
102
+ }
103
+ return value;
104
+ }
73
105
 
74
106
  // src/errors.ts
75
107
  function createErrorBody(code, message, options = {}) {
@@ -160,6 +192,414 @@ function traceIdFromTraceparent(traceparent) {
160
192
  return void 0;
161
193
  }
162
194
 
195
+ // src/tracing.ts
196
+ import {
197
+ SpanKind,
198
+ SpanStatusCode,
199
+ context,
200
+ propagation,
201
+ trace
202
+ } from "@opentelemetry/api";
203
+ import { AsyncLocalStorageContextManager } from "@opentelemetry/context-async-hooks";
204
+ var AGENTSERVER_INVOCATION_ID_BAGGAGE = "azure.ai.agentserver.invocation_id";
205
+ var AGENTSERVER_SESSION_ID_BAGGAGE = "azure.ai.agentserver.session_id";
206
+ var AGENTSERVER_CONVERSATION_ID_BAGGAGE = "azure.ai.agentserver.conversation_id";
207
+ var ATTR_SERVICE_NAME = "service.name";
208
+ var ATTR_GEN_AI_SYSTEM = "gen_ai.system";
209
+ var ATTR_GEN_AI_PROVIDER_NAME = "gen_ai.provider.name";
210
+ var ATTR_GEN_AI_AGENT_ID = "gen_ai.agent.id";
211
+ var ATTR_GEN_AI_AGENT_NAME = "gen_ai.agent.name";
212
+ var ATTR_GEN_AI_AGENT_VERSION = "gen_ai.agent.version";
213
+ var ATTR_GEN_AI_RESPONSE_ID = "gen_ai.response.id";
214
+ var ATTR_GEN_AI_OPERATION_NAME = "gen_ai.operation.name";
215
+ var ATTR_GEN_AI_CONVERSATION_ID = "gen_ai.conversation.id";
216
+ var ATTR_FOUNDRY_PROJECT_ID = "microsoft.foundry.project.id";
217
+ var ATTR_SESSION_ID = "microsoft.session.id";
218
+ var ATTR_INVOCATION_ID = "azure.ai.agentserver.invocations.id";
219
+ var ATTR_INVOCATIONS_ERROR_CODE = "azure.ai.agentserver.invocations.error.code";
220
+ var ATTR_INVOCATIONS_ERROR_MESSAGE = "azure.ai.agentserver.invocations.error.message";
221
+ var SERVICE_NAME_VALUE = "azure.ai.agentserver";
222
+ var GEN_AI_SYSTEM_VALUE = "azure.ai.agentserver";
223
+ var GEN_AI_PROVIDER_NAME_VALUE = "AzureAI Hosted Agents";
224
+ var contextManagerRegistered = false;
225
+ var headerGetter = {
226
+ keys: () => ["traceparent", "tracestate", "baggage", "x-request-id"],
227
+ get: (carrier, key) => carrier(key)
228
+ };
229
+ function startAgentServerRequestSpan(options) {
230
+ ensureAgentServerContextManager();
231
+ const config = options.config;
232
+ const instrumentationScope = options.instrumentationScope ?? "Azure.AI.AgentServer";
233
+ const operationName = options.operationName ?? options.operation;
234
+ const spanName = config.agentId ? `${options.operation} ${config.agentId}` : options.operation;
235
+ const extractedContext = propagation.extract(
236
+ context.active(),
237
+ options.headers,
238
+ headerGetter
239
+ );
240
+ const attributes = {
241
+ [ATTR_SERVICE_NAME]: config.agentName || SERVICE_NAME_VALUE,
242
+ [ATTR_GEN_AI_SYSTEM]: GEN_AI_SYSTEM_VALUE,
243
+ [ATTR_GEN_AI_PROVIDER_NAME]: GEN_AI_PROVIDER_NAME_VALUE,
244
+ [ATTR_GEN_AI_RESPONSE_ID]: options.requestId,
245
+ [ATTR_GEN_AI_AGENT_ID]: config.agentId,
246
+ [ATTR_GEN_AI_OPERATION_NAME]: operationName
247
+ };
248
+ setAttribute(attributes, ATTR_GEN_AI_AGENT_NAME, config.agentName);
249
+ setAttribute(attributes, ATTR_GEN_AI_AGENT_VERSION, config.agentVersion);
250
+ setAttribute(attributes, ATTR_FOUNDRY_PROJECT_ID, config.projectArmId);
251
+ setAttribute(attributes, ATTR_SESSION_ID, options.sessionId);
252
+ setAttribute(attributes, ATTR_INVOCATION_ID, options.invocationId);
253
+ const requestId = options.headers("x-request-id") ?? options.correlationRequestId;
254
+ setAttribute(attributes, "x_request_id", requestId);
255
+ const span = trace.getTracer(instrumentationScope).startSpan(
256
+ spanName,
257
+ {
258
+ kind: SpanKind.SERVER,
259
+ attributes
260
+ },
261
+ extractedContext
262
+ );
263
+ const spanContext = withAgentServerBaggage(
264
+ trace.setSpan(extractedContext, span),
265
+ {
266
+ invocationId: options.invocationId,
267
+ sessionId: options.sessionId,
268
+ requestId
269
+ }
270
+ );
271
+ let ended = false;
272
+ function recordError(error, code = "internal_error") {
273
+ recordAgentServerSpanError(span, error, code);
274
+ }
275
+ return {
276
+ span,
277
+ context: spanContext,
278
+ run: (fn) => context.with(spanContext, fn),
279
+ recordError,
280
+ end(error) {
281
+ if (ended) {
282
+ return;
283
+ }
284
+ ended = true;
285
+ if (error !== void 0) {
286
+ recordError(error);
287
+ }
288
+ span.end();
289
+ flushSpans();
290
+ }
291
+ };
292
+ }
293
+ function ensureAgentServerContextManager() {
294
+ if (contextManagerRegistered) {
295
+ return;
296
+ }
297
+ contextManagerRegistered = true;
298
+ const manager = new AsyncLocalStorageContextManager().enable();
299
+ const registered = context.setGlobalContextManager(manager);
300
+ if (!registered) {
301
+ manager.disable();
302
+ }
303
+ }
304
+ function withAgentServerBaggage(sourceContext, values) {
305
+ let baggage = propagation.getBaggage(sourceContext) ?? propagation.createBaggage();
306
+ if (values.invocationId) {
307
+ baggage = baggage.setEntry(AGENTSERVER_INVOCATION_ID_BAGGAGE, {
308
+ value: values.invocationId
309
+ });
310
+ }
311
+ if (values.sessionId) {
312
+ baggage = baggage.setEntry(AGENTSERVER_SESSION_ID_BAGGAGE, {
313
+ value: values.sessionId
314
+ });
315
+ }
316
+ if (values.requestId) {
317
+ baggage = baggage.setEntry("x_request_id", {
318
+ value: values.requestId
319
+ });
320
+ }
321
+ return propagation.setBaggage(sourceContext, baggage);
322
+ }
323
+ function recordAgentServerSpanError(span, error, code = "internal_error") {
324
+ const message = errorMessage(error);
325
+ span.setStatus({
326
+ code: SpanStatusCode.ERROR,
327
+ message
328
+ });
329
+ span.setAttribute("error.type", errorType(error));
330
+ span.setAttribute(ATTR_INVOCATIONS_ERROR_CODE, code);
331
+ span.setAttribute(ATTR_INVOCATIONS_ERROR_MESSAGE, message);
332
+ if (error instanceof Error) {
333
+ span.recordException(error);
334
+ } else {
335
+ span.recordException(message);
336
+ }
337
+ }
338
+ function createFoundryEnrichmentSpanProcessor(config) {
339
+ return {
340
+ onStart(span, parentContext) {
341
+ setSpanAttribute(span, ATTR_FOUNDRY_PROJECT_ID, config.projectArmId);
342
+ setSpanAttribute(span, ATTR_GEN_AI_AGENT_NAME, config.agentName);
343
+ setSpanAttribute(span, ATTR_GEN_AI_AGENT_VERSION, config.agentVersion);
344
+ setSpanAttribute(span, ATTR_GEN_AI_AGENT_ID, config.agentId);
345
+ const activeBaggage = propagation.getBaggage(
346
+ parentContext ?? context.active()
347
+ );
348
+ setSpanAttribute(
349
+ span,
350
+ ATTR_SESSION_ID,
351
+ activeBaggage?.getEntry(AGENTSERVER_SESSION_ID_BAGGAGE)?.value
352
+ );
353
+ setSpanAttribute(
354
+ span,
355
+ ATTR_INVOCATION_ID,
356
+ activeBaggage?.getEntry(AGENTSERVER_INVOCATION_ID_BAGGAGE)?.value
357
+ );
358
+ setSpanAttribute(
359
+ span,
360
+ ATTR_GEN_AI_CONVERSATION_ID,
361
+ activeBaggage?.getEntry(AGENTSERVER_CONVERSATION_ID_BAGGAGE)?.value
362
+ );
363
+ },
364
+ onEnd() {
365
+ },
366
+ shutdown() {
367
+ },
368
+ forceFlush() {
369
+ return true;
370
+ }
371
+ };
372
+ }
373
+ function flushSpans(timeoutMillis = 5e3) {
374
+ const provider = trace.getTracerProvider();
375
+ try {
376
+ void provider.forceFlush?.(timeoutMillis);
377
+ } catch {
378
+ }
379
+ }
380
+ function setAttribute(target, key, value) {
381
+ const normalized = value?.trim();
382
+ if (normalized) {
383
+ target[key] = normalized;
384
+ }
385
+ }
386
+ function setSpanAttribute(span, key, value) {
387
+ const normalized = value?.trim();
388
+ if (normalized) {
389
+ span.setAttribute(key, normalized);
390
+ }
391
+ }
392
+ function errorMessage(error) {
393
+ if (error instanceof Error) {
394
+ return error.message;
395
+ }
396
+ return String(error);
397
+ }
398
+ function errorType(error) {
399
+ if (error instanceof Error) {
400
+ return error.name || error.constructor.name || "Error";
401
+ }
402
+ return typeof error;
403
+ }
404
+
405
+ // src/observability.ts
406
+ var configuredKey;
407
+ var configuredPromise;
408
+ function configureAgentServerObservability(options) {
409
+ const key = observabilityKey(options.config);
410
+ if (!configuredPromise || configuredKey !== key) {
411
+ configuredKey = key;
412
+ configuredPromise = configureAgentServerObservabilityOnce(options);
413
+ }
414
+ return configuredPromise;
415
+ }
416
+ async function configureAgentServerObservabilityOnce(options) {
417
+ const logger = options.logger ?? silentLogger;
418
+ const config = options.config;
419
+ const exporters = [];
420
+ const shutdownCallbacks = [];
421
+ const enrichmentProcessor = createFoundryEnrichmentSpanProcessor(config);
422
+ const otlpSpanProcessor = await createOtlpSpanProcessor(config, logger);
423
+ const spanProcessors = [enrichmentProcessor];
424
+ if (otlpSpanProcessor) {
425
+ spanProcessors.push(otlpSpanProcessor);
426
+ }
427
+ if (config.applicationInsightsConnectionString.trim()) {
428
+ try {
429
+ const azureMonitor = await importOptional(
430
+ "@azure/monitor-opentelemetry"
431
+ );
432
+ const azureMonitorOptions = {
433
+ resource: await createResource(config, logger),
434
+ azureMonitorExporterOptions: {
435
+ connectionString: config.applicationInsightsConnectionString.trim()
436
+ }
437
+ };
438
+ if (spanProcessors.length > 0) {
439
+ azureMonitorOptions.spanProcessors = spanProcessors;
440
+ }
441
+ if (otlpSpanProcessor) {
442
+ exporters.push("otlp");
443
+ }
444
+ azureMonitor.useAzureMonitor(azureMonitorOptions);
445
+ exporters.push("azure-monitor");
446
+ shutdownCallbacks.push(() => azureMonitor.shutdownAzureMonitor());
447
+ logger.info("Foundry Agent Server observability configured", {
448
+ exporters
449
+ });
450
+ return makeHandle(exporters, shutdownCallbacks);
451
+ } catch (error) {
452
+ logger.warn("Failed to initialize Azure Monitor observability", {
453
+ error: formatError(error)
454
+ });
455
+ }
456
+ }
457
+ if (otlpSpanProcessor) {
458
+ try {
459
+ const setup2 = await ensureTraceProvider(config, logger, spanProcessors);
460
+ if (setup2.configured) {
461
+ exporters.push("otlp");
462
+ }
463
+ if (setup2.shutdown) {
464
+ shutdownCallbacks.push(setup2.shutdown);
465
+ }
466
+ logger.info("Foundry Agent Server OTLP observability configured", {
467
+ exporters
468
+ });
469
+ return makeHandle(exporters, shutdownCallbacks, setup2.configured);
470
+ } catch (error) {
471
+ logger.warn("Failed to initialize OTLP observability", {
472
+ error: formatError(error)
473
+ });
474
+ }
475
+ }
476
+ const setup = await ensureTraceProvider(config, logger, [
477
+ enrichmentProcessor
478
+ ]);
479
+ if (setup.shutdown) {
480
+ shutdownCallbacks.push(setup.shutdown);
481
+ }
482
+ return makeHandle(exporters, shutdownCallbacks, setup.configured);
483
+ }
484
+ async function createOtlpSpanProcessor(config, logger) {
485
+ const endpoint = config.otlpEndpoint.trim();
486
+ if (!endpoint) {
487
+ return void 0;
488
+ }
489
+ try {
490
+ const [{ OTLPTraceExporter }, { BatchSpanProcessor }] = await Promise.all([
491
+ importOptional(
492
+ "@opentelemetry/exporter-trace-otlp-http"
493
+ ),
494
+ importOptional(
495
+ "@opentelemetry/sdk-trace-base"
496
+ )
497
+ ]);
498
+ return new BatchSpanProcessor(new OTLPTraceExporter({ url: endpoint }));
499
+ } catch (error) {
500
+ logger.warn("Failed to initialize OTLP span processor", {
501
+ error: formatError(error)
502
+ });
503
+ return void 0;
504
+ }
505
+ }
506
+ async function createResource(config, logger) {
507
+ const attributes = {
508
+ "service.name": config.agentName || "azure.ai.agentserver"
509
+ };
510
+ if (config.agentVersion) {
511
+ attributes["service.version"] = config.agentVersion;
512
+ }
513
+ if (config.projectArmId) {
514
+ attributes["microsoft.foundry.project.id"] = config.projectArmId;
515
+ }
516
+ try {
517
+ const resources = await importOptional(
518
+ "@opentelemetry/resources"
519
+ );
520
+ return resources.resourceFromAttributes(attributes);
521
+ } catch (error) {
522
+ logger.debug?.("OpenTelemetry resource package unavailable", {
523
+ error: formatError(error)
524
+ });
525
+ return void 0;
526
+ }
527
+ }
528
+ function makeHandle(exporters, shutdownCallbacks, providerConfigured = exporters.length > 0) {
529
+ return {
530
+ configured: providerConfigured,
531
+ exporters: [...exporters],
532
+ async shutdown() {
533
+ await Promise.allSettled(shutdownCallbacks.map((shutdown) => shutdown()));
534
+ }
535
+ };
536
+ }
537
+ async function ensureTraceProvider(config, logger, spanProcessors) {
538
+ try {
539
+ const api = await importOptional("@opentelemetry/api");
540
+ const currentProvider = api.trace.getTracerProvider();
541
+ if (currentProvider.addSpanProcessor) {
542
+ for (const processor of spanProcessors) {
543
+ currentProvider.addSpanProcessor(processor);
544
+ }
545
+ return { configured: true };
546
+ }
547
+ } catch (error) {
548
+ logger.debug?.("OpenTelemetry API provider inspection failed", {
549
+ error: formatError(error)
550
+ });
551
+ }
552
+ try {
553
+ const traceNode = await importOptional(
554
+ "@opentelemetry/sdk-trace-node"
555
+ );
556
+ const provider = new traceNode.NodeTracerProvider({
557
+ resource: await createResource(config, logger),
558
+ spanProcessors
559
+ });
560
+ provider.register();
561
+ return {
562
+ configured: true,
563
+ shutdown: () => provider.shutdown()
564
+ };
565
+ } catch (error) {
566
+ logger.warn("Failed to initialize local OpenTelemetry tracer provider", {
567
+ error: formatError(error)
568
+ });
569
+ return { configured: false };
570
+ }
571
+ }
572
+ async function importOptional(specifier) {
573
+ const dynamicImport = new Function(
574
+ "specifier",
575
+ "return import(specifier)"
576
+ );
577
+ return await dynamicImport(specifier);
578
+ }
579
+ function formatError(error) {
580
+ if (error instanceof Error) {
581
+ return error.stack ?? error.message;
582
+ }
583
+ return String(error);
584
+ }
585
+ var silentLogger = {
586
+ info: () => {
587
+ },
588
+ warn: () => {
589
+ }
590
+ };
591
+ function observabilityKey(config) {
592
+ const appInsights = config.applicationInsightsConnectionString.trim();
593
+ const otlp = config.otlpEndpoint.trim();
594
+ return [
595
+ appInsights,
596
+ otlp,
597
+ config.agentName,
598
+ config.agentVersion,
599
+ config.projectArmId
600
+ ].join("|");
601
+ }
602
+
163
603
  // src/server-version.ts
164
604
  function buildServerVersionSegment(name, version) {
165
605
  if (!name.trim()) {
@@ -184,7 +624,24 @@ function readPackageVersion() {
184
624
  }
185
625
  var AGENTSERVER_CORE_PACKAGE_VERSION = readPackageVersion();
186
626
  export {
627
+ AGENTSERVER_CONVERSATION_ID_BAGGAGE,
187
628
  AGENTSERVER_CORE_PACKAGE_VERSION,
629
+ AGENTSERVER_INVOCATION_ID_BAGGAGE,
630
+ AGENTSERVER_SESSION_ID_BAGGAGE,
631
+ ATTR_FOUNDRY_PROJECT_ID,
632
+ ATTR_GEN_AI_AGENT_ID,
633
+ ATTR_GEN_AI_AGENT_NAME,
634
+ ATTR_GEN_AI_AGENT_VERSION,
635
+ ATTR_GEN_AI_CONVERSATION_ID,
636
+ ATTR_GEN_AI_OPERATION_NAME,
637
+ ATTR_GEN_AI_PROVIDER_NAME,
638
+ ATTR_GEN_AI_RESPONSE_ID,
639
+ ATTR_GEN_AI_SYSTEM,
640
+ ATTR_INVOCATIONS_ERROR_CODE,
641
+ ATTR_INVOCATIONS_ERROR_MESSAGE,
642
+ ATTR_INVOCATION_ID,
643
+ ATTR_SERVICE_NAME,
644
+ ATTR_SESSION_ID,
188
645
  CHAT_ISOLATION_HEADER,
189
646
  INVOCATION_ID_HEADER,
190
647
  INVOCATION_ID_HEADERS,
@@ -194,11 +651,19 @@ export {
194
651
  USER_ISOLATION_HEADER,
195
652
  buildPlatformServerHeader,
196
653
  buildServerVersionSegment,
654
+ configureAgentServerObservability,
197
655
  createErrorBody,
656
+ createFoundryEnrichmentSpanProcessor,
657
+ flushSpans,
658
+ recordAgentServerSpanError,
198
659
  resolveAgentServerConfig,
660
+ resolveGracefulShutdownTimeout,
199
661
  resolveInvocationIdentity,
662
+ resolveLogLevel,
200
663
  resolvePort,
201
664
  resolveRequestId,
202
665
  resolveSseKeepAliveInterval,
203
- sanitizeProtocolId
666
+ sanitizeProtocolId,
667
+ startAgentServerRequestSpan,
668
+ withAgentServerBaggage
204
669
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@cuylabs/agent-foundry-agentserver-core",
3
- "version": "4.1.0",
3
+ "version": "4.2.0",
4
4
  "description": "Shared TypeScript host utilities for Foundry Agent Server protocol packages",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",
@@ -16,6 +16,17 @@
16
16
  "dist",
17
17
  "README.md"
18
18
  ],
19
+ "dependencies": {
20
+ "@opentelemetry/api": "^1.9.0",
21
+ "@opentelemetry/context-async-hooks": "^2.6.0"
22
+ },
23
+ "optionalDependencies": {
24
+ "@azure/monitor-opentelemetry": "^1.16.0",
25
+ "@opentelemetry/exporter-trace-otlp-http": "^0.213.0",
26
+ "@opentelemetry/resources": "^2.6.0",
27
+ "@opentelemetry/sdk-trace-base": "^2.6.0",
28
+ "@opentelemetry/sdk-trace-node": "^2.6.0"
29
+ },
19
30
  "devDependencies": {
20
31
  "@types/node": "^22.0.0",
21
32
  "tsup": "^8.0.0",