@cuylabs/agent-foundry-agentserver-core 4.1.0 → 4.3.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 +23 -0
- package/dist/index.d.ts +78 -1
- package/dist/index.js +467 -2
- package/package.json +12 -1
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
|
-
|
|
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.
|
|
3
|
+
"version": "4.3.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",
|