@cuylabs/agent-foundry-agentserver-core 4.8.0 → 4.9.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 +22 -2
- package/dist/index.d.ts +56 -1
- package/dist/index.js +276 -13
- package/package.json +4 -2
package/README.md
CHANGED
|
@@ -4,8 +4,10 @@ Shared host utilities for TypeScript Foundry Agent Server protocol packages.
|
|
|
4
4
|
|
|
5
5
|
This package mirrors the protocol-agnostic parts of Python
|
|
6
6
|
`azure-ai-agentserver-core`: configuration resolution, request/session ID
|
|
7
|
-
sanitization, request correlation IDs, server-version header formatting,
|
|
8
|
-
standard error envelopes
|
|
7
|
+
sanitization, request correlation IDs, server-version header formatting,
|
|
8
|
+
standard error envelopes, common Express host middleware, readiness/liveness
|
|
9
|
+
handlers, request logging, graceful shutdown helpers, and startup configuration
|
|
10
|
+
logging. Protocol packages such as
|
|
9
11
|
`@cuylabs/agent-foundry-agentserver-invocations` build their HTTP surface on top
|
|
10
12
|
of these helpers.
|
|
11
13
|
|
|
@@ -18,6 +20,10 @@ sets the Foundry baggage keys used by the Python host:
|
|
|
18
20
|
```text
|
|
19
21
|
azure.ai.agentserver.invocation_id
|
|
20
22
|
azure.ai.agentserver.session_id
|
|
23
|
+
azure.ai.agentserver.response_id
|
|
24
|
+
azure.ai.agentserver.conversation_id
|
|
25
|
+
azure.ai.agentserver.streaming
|
|
26
|
+
azure.ai.agentserver.x-request-id
|
|
21
27
|
```
|
|
22
28
|
|
|
23
29
|
Exporter setup is optional and is driven by the same environment variables used
|
|
@@ -31,3 +37,17 @@ by hosted agents:
|
|
|
31
37
|
Protocol packages call `configureAgentServerObservability()` by default. Apps
|
|
32
38
|
that already own OpenTelemetry setup can disable package-managed exporter setup
|
|
33
39
|
at the protocol layer and keep the span/baggage helpers.
|
|
40
|
+
|
|
41
|
+
## Python Comparison
|
|
42
|
+
|
|
43
|
+
Compare this package to Python `azure-ai-agentserver-core`, not to a protocol
|
|
44
|
+
package. Python exposes an `AgentServerHost` built on ASGI, Starlette, and
|
|
45
|
+
Hypercorn. This TypeScript package exposes the equivalent protocol-agnostic
|
|
46
|
+
host pieces for Express and Node HTTP, then protocol packages compose those
|
|
47
|
+
pieces into their own server entry points.
|
|
48
|
+
|
|
49
|
+
`@cuylabs/agent-foundry-agentserver-responses` depends on this package in the
|
|
50
|
+
same way Python `azure-ai-agentserver-responses` depends on
|
|
51
|
+
`azure-ai-agentserver-core`. The local `@cuylabs/agent-foundry-hosting`
|
|
52
|
+
package is different: it is agents-ts runtime glue, not a direct Python SDK
|
|
53
|
+
peer.
|
package/dist/index.d.ts
CHANGED
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import { Server } from 'node:http';
|
|
2
|
+
import { Request, Response, NextFunction, Express } from 'express';
|
|
1
3
|
import { Span, Context } from '@opentelemetry/api';
|
|
2
4
|
|
|
3
5
|
interface AgentServerConfig {
|
|
@@ -36,11 +38,14 @@ interface AgentServerErrorBody {
|
|
|
36
38
|
message: string;
|
|
37
39
|
type?: string;
|
|
38
40
|
details?: Array<Record<string, unknown>>;
|
|
41
|
+
additionalInfo?: Record<string, unknown>;
|
|
39
42
|
};
|
|
40
43
|
}
|
|
41
44
|
declare function createErrorBody(code: string, message: string, options?: {
|
|
42
45
|
type?: string;
|
|
43
46
|
details?: Array<Record<string, unknown>>;
|
|
47
|
+
additionalInfo?: Record<string, unknown>;
|
|
48
|
+
requestId?: string;
|
|
44
49
|
}): AgentServerErrorBody;
|
|
45
50
|
|
|
46
51
|
declare const REQUEST_ID_HEADER: "x-request-id";
|
|
@@ -48,6 +53,7 @@ declare const INVOCATION_ID_HEADER: "x-agent-invocation-id";
|
|
|
48
53
|
declare const SESSION_ID_HEADER: "x-agent-session-id";
|
|
49
54
|
declare const USER_ISOLATION_HEADER: "x-agent-user-isolation-key";
|
|
50
55
|
declare const CHAT_ISOLATION_HEADER: "x-agent-chat-isolation-key";
|
|
56
|
+
declare const CLIENT_HEADER_PREFIX: "x-client-";
|
|
51
57
|
declare const SESSION_ID_HEADERS: readonly ["x-agent-session-id", "x-foundry-agent-session-id", "x-ms-agent-session-id"];
|
|
52
58
|
declare const INVOCATION_ID_HEADERS: readonly ["x-agent-invocation-id", "x-foundry-invocation-id", "x-ms-invocation-id", "x-invocation-id"];
|
|
53
59
|
type HeaderGetter = (name: string) => string | undefined;
|
|
@@ -66,10 +72,47 @@ interface InvocationIdentity {
|
|
|
66
72
|
sessionId: string;
|
|
67
73
|
invocationId: string;
|
|
68
74
|
}
|
|
75
|
+
interface AgentIsolationContext {
|
|
76
|
+
userKey?: string;
|
|
77
|
+
chatKey?: string;
|
|
78
|
+
}
|
|
69
79
|
declare function resolveInvocationIdentity(input: InvocationIdentityInput): InvocationIdentity;
|
|
70
80
|
declare function resolveRequestId(header: HeaderGetter): string;
|
|
81
|
+
declare function resolveAgentIsolation(header: HeaderGetter): AgentIsolationContext;
|
|
82
|
+
declare function collectClientHeaders(headers: Record<string, string | string[] | undefined>): Record<string, string>;
|
|
71
83
|
declare function sanitizeProtocolId(value: string | undefined, fallback: string): string;
|
|
72
84
|
|
|
85
|
+
interface AgentServerLogger {
|
|
86
|
+
info(message: string, meta?: Record<string, unknown>): void;
|
|
87
|
+
warn(message: string, meta?: Record<string, unknown>): void;
|
|
88
|
+
error(message: string, meta?: Record<string, unknown>): void;
|
|
89
|
+
debug?(message: string, meta?: Record<string, unknown>): void;
|
|
90
|
+
}
|
|
91
|
+
interface AgentServerExpressRequest extends Request {
|
|
92
|
+
agentServerRequestId?: string;
|
|
93
|
+
}
|
|
94
|
+
interface AgentServerRequestHeadersMiddlewareOptions {
|
|
95
|
+
platformServer: string;
|
|
96
|
+
}
|
|
97
|
+
declare function agentServerRequestHeadersMiddleware(options: AgentServerRequestHeadersMiddlewareOptions): (req: Request, res: Response, next: NextFunction) => void;
|
|
98
|
+
declare function addProtectedHeaders(res: Response, headers: Record<string, string>, options?: {
|
|
99
|
+
sanitizeHeaderNames?: readonly string[];
|
|
100
|
+
}): void;
|
|
101
|
+
declare function agentServerLoggingMiddleware(logger: AgentServerLogger): (req: Request, res: Response, next: NextFunction) => void;
|
|
102
|
+
declare function readinessHandler(_req: Request, res: Response): void;
|
|
103
|
+
declare function healthzHandler(_req: Request, res: Response): void;
|
|
104
|
+
declare function methodNotAllowed(allowedMethods: readonly string[]): (_req: Request, res: Response) => void;
|
|
105
|
+
declare function notFoundMiddleware(): (_req: Request, res: Response, _next: NextFunction) => void;
|
|
106
|
+
declare function errorMiddleware(logger: AgentServerLogger): (err: unknown, req: Request, res: Response, next: NextFunction) => void;
|
|
107
|
+
declare function closeServerWithTimeout(server: Server, timeoutSeconds: number): Promise<void>;
|
|
108
|
+
declare function configureExpressAgentServerApp(app: Express, options?: {
|
|
109
|
+
trustProxy?: boolean | number | string;
|
|
110
|
+
}): void;
|
|
111
|
+
declare function logAgentServerStartupConfiguration(logger: AgentServerLogger, config: AgentServerConfig, platformServer: string): void;
|
|
112
|
+
declare function maskUri(uri: string): string;
|
|
113
|
+
declare function formatError(error: unknown): string;
|
|
114
|
+
declare function defaultAgentServerLogger(scope: string): AgentServerLogger;
|
|
115
|
+
|
|
73
116
|
interface AgentServerObservabilityLogger {
|
|
74
117
|
info(message: string, meta?: Record<string, unknown>): void;
|
|
75
118
|
warn(message: string, meta?: Record<string, unknown>): void;
|
|
@@ -98,6 +141,9 @@ declare const AGENTSERVER_CORE_PACKAGE_VERSION: string;
|
|
|
98
141
|
declare const AGENTSERVER_INVOCATION_ID_BAGGAGE = "azure.ai.agentserver.invocation_id";
|
|
99
142
|
declare const AGENTSERVER_SESSION_ID_BAGGAGE = "azure.ai.agentserver.session_id";
|
|
100
143
|
declare const AGENTSERVER_CONVERSATION_ID_BAGGAGE = "azure.ai.agentserver.conversation_id";
|
|
144
|
+
declare const AGENTSERVER_RESPONSE_ID_BAGGAGE = "azure.ai.agentserver.response_id";
|
|
145
|
+
declare const AGENTSERVER_STREAMING_BAGGAGE = "azure.ai.agentserver.streaming";
|
|
146
|
+
declare const AGENTSERVER_REQUEST_ID_BAGGAGE = "azure.ai.agentserver.x-request-id";
|
|
101
147
|
declare const ATTR_SERVICE_NAME = "service.name";
|
|
102
148
|
declare const ATTR_GEN_AI_SYSTEM = "gen_ai.system";
|
|
103
149
|
declare const ATTR_GEN_AI_PROVIDER_NAME = "gen_ai.provider.name";
|
|
@@ -112,6 +158,9 @@ declare const ATTR_SESSION_ID = "microsoft.session.id";
|
|
|
112
158
|
declare const ATTR_INVOCATION_ID = "azure.ai.agentserver.invocations.id";
|
|
113
159
|
declare const ATTR_INVOCATIONS_ERROR_CODE = "azure.ai.agentserver.invocations.error.code";
|
|
114
160
|
declare const ATTR_INVOCATIONS_ERROR_MESSAGE = "azure.ai.agentserver.invocations.error.message";
|
|
161
|
+
declare const ATTR_RESPONSES_STREAMING = "azure.ai.agentserver.responses.streaming";
|
|
162
|
+
declare const ATTR_RESPONSES_ERROR_CODE = "azure.ai.agentserver.responses.error.code";
|
|
163
|
+
declare const ATTR_RESPONSES_ERROR_MESSAGE = "azure.ai.agentserver.responses.error.message";
|
|
115
164
|
interface AgentServerRequestSpanOptions {
|
|
116
165
|
config: Pick<AgentServerConfig, "agentId" | "agentName" | "agentVersion" | "projectArmId">;
|
|
117
166
|
headers: HeaderGetter;
|
|
@@ -121,6 +170,9 @@ interface AgentServerRequestSpanOptions {
|
|
|
121
170
|
instrumentationScope?: string;
|
|
122
171
|
invocationId?: string;
|
|
123
172
|
sessionId?: string;
|
|
173
|
+
responseId?: string;
|
|
174
|
+
conversationId?: string;
|
|
175
|
+
streaming?: boolean | string;
|
|
124
176
|
correlationRequestId?: string;
|
|
125
177
|
}
|
|
126
178
|
interface AgentServerRequestSpan {
|
|
@@ -140,10 +192,13 @@ declare function startAgentServerRequestSpan(options: AgentServerRequestSpanOpti
|
|
|
140
192
|
declare function withAgentServerBaggage(sourceContext: Context, values: {
|
|
141
193
|
invocationId?: string;
|
|
142
194
|
sessionId?: string;
|
|
195
|
+
responseId?: string;
|
|
196
|
+
conversationId?: string;
|
|
197
|
+
streaming?: boolean | string;
|
|
143
198
|
requestId?: string;
|
|
144
199
|
}): Context;
|
|
145
200
|
declare function recordAgentServerSpanError(span: Span, error: unknown, code?: string): void;
|
|
146
201
|
declare function createFoundryEnrichmentSpanProcessor(config: Pick<AgentServerConfig, "agentId" | "agentName" | "agentVersion" | "projectArmId">): AgentServerSpanProcessor;
|
|
147
202
|
declare function flushSpans(timeoutMillis?: number): void;
|
|
148
203
|
|
|
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 };
|
|
204
|
+
export { AGENTSERVER_CONVERSATION_ID_BAGGAGE, AGENTSERVER_CORE_PACKAGE_VERSION, AGENTSERVER_INVOCATION_ID_BAGGAGE, AGENTSERVER_REQUEST_ID_BAGGAGE, AGENTSERVER_RESPONSE_ID_BAGGAGE, AGENTSERVER_SESSION_ID_BAGGAGE, AGENTSERVER_STREAMING_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_RESPONSES_ERROR_CODE, ATTR_RESPONSES_ERROR_MESSAGE, ATTR_RESPONSES_STREAMING, ATTR_SERVICE_NAME, ATTR_SESSION_ID, type AgentIsolationContext, type AgentServerConfig, type AgentServerConfigOptions, type AgentServerErrorBody, type AgentServerExpressRequest, type AgentServerLogLevel, type AgentServerLogger, type AgentServerObservabilityHandle, type AgentServerObservabilityLogger, type AgentServerRequestSpan, type AgentServerRequestSpanOptions, type AgentServerSpanProcessor, CHAT_ISOLATION_HEADER, CLIENT_HEADER_PREFIX, 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, addProtectedHeaders, agentServerLoggingMiddleware, agentServerRequestHeadersMiddleware, buildPlatformServerHeader, buildServerVersionSegment, closeServerWithTimeout, collectClientHeaders, configureAgentServerObservability, configureExpressAgentServerApp, createErrorBody, createFoundryEnrichmentSpanProcessor, defaultAgentServerLogger, errorMiddleware, flushSpans, formatError, healthzHandler, logAgentServerStartupConfiguration, maskUri, methodNotAllowed, notFoundMiddleware, readinessHandler, recordAgentServerSpanError, resolveAgentIsolation, resolveAgentServerConfig, resolveGracefulShutdownTimeout, resolveInvocationIdentity, resolveLogLevel, resolvePort, resolveRequestId, resolveSseKeepAliveInterval, sanitizeProtocolId, startAgentServerRequestSpan, withAgentServerBaggage };
|
package/dist/index.js
CHANGED
|
@@ -105,12 +105,17 @@ function validateInteger(value, source) {
|
|
|
105
105
|
|
|
106
106
|
// src/errors.ts
|
|
107
107
|
function createErrorBody(code, message, options = {}) {
|
|
108
|
+
const additionalInfo = {
|
|
109
|
+
...options.additionalInfo ?? {},
|
|
110
|
+
...options.requestId ? { request_id: options.requestId } : {}
|
|
111
|
+
};
|
|
108
112
|
return {
|
|
109
113
|
error: {
|
|
110
114
|
code,
|
|
111
115
|
message,
|
|
112
116
|
...options.type ? { type: options.type } : {},
|
|
113
|
-
...options.details ? { details: options.details } : {}
|
|
117
|
+
...options.details ? { details: options.details } : {},
|
|
118
|
+
...Object.keys(additionalInfo).length > 0 ? { additionalInfo } : {}
|
|
114
119
|
}
|
|
115
120
|
};
|
|
116
121
|
}
|
|
@@ -122,6 +127,7 @@ var INVOCATION_ID_HEADER = "x-agent-invocation-id";
|
|
|
122
127
|
var SESSION_ID_HEADER = "x-agent-session-id";
|
|
123
128
|
var USER_ISOLATION_HEADER = "x-agent-user-isolation-key";
|
|
124
129
|
var CHAT_ISOLATION_HEADER = "x-agent-chat-isolation-key";
|
|
130
|
+
var CLIENT_HEADER_PREFIX = "x-client-";
|
|
125
131
|
var SESSION_ID_HEADERS = [
|
|
126
132
|
SESSION_ID_HEADER,
|
|
127
133
|
"x-foundry-agent-session-id",
|
|
@@ -155,6 +161,28 @@ function resolveRequestId(header) {
|
|
|
155
161
|
const incoming = header(REQUEST_ID_HEADER)?.trim();
|
|
156
162
|
return incoming || randomUUID().replaceAll("-", "");
|
|
157
163
|
}
|
|
164
|
+
function resolveAgentIsolation(header) {
|
|
165
|
+
const userKey = header(USER_ISOLATION_HEADER);
|
|
166
|
+
const chatKey = header(CHAT_ISOLATION_HEADER);
|
|
167
|
+
return {
|
|
168
|
+
...userKey !== void 0 ? { userKey } : {},
|
|
169
|
+
...chatKey !== void 0 ? { chatKey } : {}
|
|
170
|
+
};
|
|
171
|
+
}
|
|
172
|
+
function collectClientHeaders(headers) {
|
|
173
|
+
const clientHeaders = {};
|
|
174
|
+
for (const [name, value] of Object.entries(headers)) {
|
|
175
|
+
const lowerName = name.toLowerCase();
|
|
176
|
+
if (!lowerName.startsWith(CLIENT_HEADER_PREFIX)) {
|
|
177
|
+
continue;
|
|
178
|
+
}
|
|
179
|
+
const headerValue = Array.isArray(value) ? value.join(",") : value;
|
|
180
|
+
if (headerValue !== void 0) {
|
|
181
|
+
clientHeaders[lowerName] = headerValue;
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
return clientHeaders;
|
|
185
|
+
}
|
|
158
186
|
function sanitizeProtocolId(value, fallback) {
|
|
159
187
|
const normalized = value?.trim();
|
|
160
188
|
if (!normalized || normalized.length > MAX_ID_LENGTH || !VALID_ID_RE.test(normalized)) {
|
|
@@ -192,6 +220,190 @@ function traceIdFromTraceparent(traceparent) {
|
|
|
192
220
|
return void 0;
|
|
193
221
|
}
|
|
194
222
|
|
|
223
|
+
// src/host.ts
|
|
224
|
+
function agentServerRequestHeadersMiddleware(options) {
|
|
225
|
+
return (req, res, next) => {
|
|
226
|
+
const requestId = resolveRequestId((name) => req.header(name));
|
|
227
|
+
req.agentServerRequestId = requestId;
|
|
228
|
+
addProtectedHeaders(res, {
|
|
229
|
+
[REQUEST_ID_HEADER]: requestId,
|
|
230
|
+
"x-platform-server": options.platformServer
|
|
231
|
+
});
|
|
232
|
+
next();
|
|
233
|
+
};
|
|
234
|
+
}
|
|
235
|
+
function addProtectedHeaders(res, headers, options = {}) {
|
|
236
|
+
const protectedRes = res;
|
|
237
|
+
if (!protectedRes.__agentServerProtectedHeaders) {
|
|
238
|
+
const protectedHeaders = /* @__PURE__ */ new Map();
|
|
239
|
+
const originalSetHeader2 = res.setHeader.bind(res);
|
|
240
|
+
protectedRes.__agentServerProtectedHeaders = protectedHeaders;
|
|
241
|
+
protectedRes.__agentServerOriginalSetHeader = originalSetHeader2;
|
|
242
|
+
res.setHeader = ((name, value) => {
|
|
243
|
+
const protectedValue = protectedHeaders.get(name.toLowerCase());
|
|
244
|
+
if (protectedValue !== void 0) {
|
|
245
|
+
return originalSetHeader2(name, protectedValue);
|
|
246
|
+
}
|
|
247
|
+
return originalSetHeader2(name, value);
|
|
248
|
+
});
|
|
249
|
+
}
|
|
250
|
+
const originalSetHeader = protectedRes.__agentServerOriginalSetHeader ?? res.setHeader.bind(res);
|
|
251
|
+
const sanitizeHeaderNames = new Set(
|
|
252
|
+
(options.sanitizeHeaderNames ?? [INVOCATION_ID_HEADER, SESSION_ID_HEADER]).map((name) => name.toLowerCase())
|
|
253
|
+
);
|
|
254
|
+
for (const [name, value] of Object.entries(headers)) {
|
|
255
|
+
const lowerName = name.toLowerCase();
|
|
256
|
+
const safeValue = sanitizeHeaderNames.has(lowerName) ? sanitizeProtocolId(value, value) : value;
|
|
257
|
+
protectedRes.__agentServerProtectedHeaders.set(lowerName, safeValue);
|
|
258
|
+
originalSetHeader(name, safeValue);
|
|
259
|
+
}
|
|
260
|
+
}
|
|
261
|
+
function agentServerLoggingMiddleware(logger) {
|
|
262
|
+
return (req, res, next) => {
|
|
263
|
+
const start = Date.now();
|
|
264
|
+
res.on("finish", () => {
|
|
265
|
+
logger.info("http request", {
|
|
266
|
+
method: req.method,
|
|
267
|
+
path: req.path,
|
|
268
|
+
statusCode: res.statusCode,
|
|
269
|
+
durationMs: Date.now() - start,
|
|
270
|
+
requestId: req.agentServerRequestId,
|
|
271
|
+
clientRequestId: req.header("x-ms-client-request-id")
|
|
272
|
+
});
|
|
273
|
+
});
|
|
274
|
+
next();
|
|
275
|
+
};
|
|
276
|
+
}
|
|
277
|
+
function readinessHandler(_req, res) {
|
|
278
|
+
res.json({ status: "healthy" });
|
|
279
|
+
}
|
|
280
|
+
function healthzHandler(_req, res) {
|
|
281
|
+
res.json({ ok: true });
|
|
282
|
+
}
|
|
283
|
+
function methodNotAllowed(allowedMethods) {
|
|
284
|
+
return (_req, res) => {
|
|
285
|
+
res.setHeader("Allow", allowedMethods.join(", "));
|
|
286
|
+
res.status(405).json(
|
|
287
|
+
createErrorBody(
|
|
288
|
+
"method_not_allowed",
|
|
289
|
+
`Allowed methods: ${allowedMethods.join(", ")}`
|
|
290
|
+
)
|
|
291
|
+
);
|
|
292
|
+
};
|
|
293
|
+
}
|
|
294
|
+
function notFoundMiddleware() {
|
|
295
|
+
return (_req, res, _next) => {
|
|
296
|
+
res.status(404).json(createErrorBody("not_found", "No route matched this path."));
|
|
297
|
+
};
|
|
298
|
+
}
|
|
299
|
+
function errorMiddleware(logger) {
|
|
300
|
+
return (err, req, res, _next) => {
|
|
301
|
+
logger.error("Unhandled middleware error", { error: formatError(err) });
|
|
302
|
+
if (res.headersSent) {
|
|
303
|
+
res.end();
|
|
304
|
+
return;
|
|
305
|
+
}
|
|
306
|
+
res.status(500).json(
|
|
307
|
+
createErrorBody(
|
|
308
|
+
"internal_error",
|
|
309
|
+
err instanceof Error ? err.message : String(err),
|
|
310
|
+
{
|
|
311
|
+
requestId: req.agentServerRequestId
|
|
312
|
+
}
|
|
313
|
+
)
|
|
314
|
+
);
|
|
315
|
+
};
|
|
316
|
+
}
|
|
317
|
+
async function closeServerWithTimeout(server, timeoutSeconds) {
|
|
318
|
+
const closePromise = new Promise((resolve, reject) => {
|
|
319
|
+
server.close((error) => {
|
|
320
|
+
if (!error || error.code === "ERR_SERVER_NOT_RUNNING") {
|
|
321
|
+
resolve();
|
|
322
|
+
return;
|
|
323
|
+
}
|
|
324
|
+
reject(error);
|
|
325
|
+
});
|
|
326
|
+
});
|
|
327
|
+
if (timeoutSeconds <= 0) {
|
|
328
|
+
await closePromise;
|
|
329
|
+
return;
|
|
330
|
+
}
|
|
331
|
+
let timeout;
|
|
332
|
+
const timeoutPromise = new Promise((resolve) => {
|
|
333
|
+
timeout = setTimeout(() => {
|
|
334
|
+
server.closeAllConnections?.();
|
|
335
|
+
resolve();
|
|
336
|
+
}, timeoutSeconds * 1e3);
|
|
337
|
+
});
|
|
338
|
+
try {
|
|
339
|
+
await Promise.race([closePromise, timeoutPromise]);
|
|
340
|
+
} finally {
|
|
341
|
+
if (timeout) {
|
|
342
|
+
clearTimeout(timeout);
|
|
343
|
+
}
|
|
344
|
+
}
|
|
345
|
+
}
|
|
346
|
+
function configureExpressAgentServerApp(app, options = {}) {
|
|
347
|
+
app.disable("x-powered-by");
|
|
348
|
+
app.set("trust proxy", options.trustProxy ?? 1);
|
|
349
|
+
}
|
|
350
|
+
function logAgentServerStartupConfiguration(logger, config, platformServer) {
|
|
351
|
+
logger.info("Foundry Agent Server platform environment", {
|
|
352
|
+
isHosted: config.isHosted,
|
|
353
|
+
agentName: config.agentName || "(not set)",
|
|
354
|
+
agentVersion: config.agentVersion || "(not set)",
|
|
355
|
+
port: config.port,
|
|
356
|
+
sessionId: config.sessionId || "(not set)",
|
|
357
|
+
sseKeepAliveIntervalSeconds: config.sseKeepAliveIntervalSeconds > 0 ? config.sseKeepAliveIntervalSeconds : "disabled"
|
|
358
|
+
});
|
|
359
|
+
logger.info("Foundry Agent Server connectivity", {
|
|
360
|
+
projectEndpoint: maskUri(config.projectEndpoint),
|
|
361
|
+
otlpEndpoint: maskUri(config.otlpEndpoint),
|
|
362
|
+
applicationInsightsConfigured: Boolean(
|
|
363
|
+
config.applicationInsightsConnectionString.trim()
|
|
364
|
+
)
|
|
365
|
+
});
|
|
366
|
+
logger.info("Foundry Agent Server host options", {
|
|
367
|
+
gracefulShutdownTimeoutSeconds: config.gracefulShutdownTimeoutSeconds,
|
|
368
|
+
platformServer
|
|
369
|
+
});
|
|
370
|
+
}
|
|
371
|
+
function maskUri(uri) {
|
|
372
|
+
const normalized = uri.trim();
|
|
373
|
+
if (!normalized) {
|
|
374
|
+
return "(not set)";
|
|
375
|
+
}
|
|
376
|
+
try {
|
|
377
|
+
const parsed = new URL(normalized);
|
|
378
|
+
return `${parsed.protocol}//${parsed.host}`;
|
|
379
|
+
} catch {
|
|
380
|
+
return "(redacted)";
|
|
381
|
+
}
|
|
382
|
+
}
|
|
383
|
+
function formatError(error) {
|
|
384
|
+
if (error instanceof Error) {
|
|
385
|
+
return error.stack ?? error.message;
|
|
386
|
+
}
|
|
387
|
+
return String(error);
|
|
388
|
+
}
|
|
389
|
+
function defaultAgentServerLogger(scope) {
|
|
390
|
+
return {
|
|
391
|
+
info: (message, meta) => {
|
|
392
|
+
if (meta) {
|
|
393
|
+
console.log(`[${scope}] ${message}`, meta);
|
|
394
|
+
} else {
|
|
395
|
+
console.log(`[${scope}] ${message}`);
|
|
396
|
+
}
|
|
397
|
+
},
|
|
398
|
+
warn: (message, meta) => {
|
|
399
|
+
console.warn(`[${scope}] ${message}`, meta ?? "");
|
|
400
|
+
},
|
|
401
|
+
error: (message, meta) => {
|
|
402
|
+
console.error(`[${scope}] ${message}`, meta ?? "");
|
|
403
|
+
}
|
|
404
|
+
};
|
|
405
|
+
}
|
|
406
|
+
|
|
195
407
|
// src/tracing.ts
|
|
196
408
|
import {
|
|
197
409
|
SpanKind,
|
|
@@ -204,6 +416,9 @@ import { AsyncLocalStorageContextManager } from "@opentelemetry/context-async-ho
|
|
|
204
416
|
var AGENTSERVER_INVOCATION_ID_BAGGAGE = "azure.ai.agentserver.invocation_id";
|
|
205
417
|
var AGENTSERVER_SESSION_ID_BAGGAGE = "azure.ai.agentserver.session_id";
|
|
206
418
|
var AGENTSERVER_CONVERSATION_ID_BAGGAGE = "azure.ai.agentserver.conversation_id";
|
|
419
|
+
var AGENTSERVER_RESPONSE_ID_BAGGAGE = "azure.ai.agentserver.response_id";
|
|
420
|
+
var AGENTSERVER_STREAMING_BAGGAGE = "azure.ai.agentserver.streaming";
|
|
421
|
+
var AGENTSERVER_REQUEST_ID_BAGGAGE = "azure.ai.agentserver.x-request-id";
|
|
207
422
|
var ATTR_SERVICE_NAME = "service.name";
|
|
208
423
|
var ATTR_GEN_AI_SYSTEM = "gen_ai.system";
|
|
209
424
|
var ATTR_GEN_AI_PROVIDER_NAME = "gen_ai.provider.name";
|
|
@@ -218,6 +433,9 @@ var ATTR_SESSION_ID = "microsoft.session.id";
|
|
|
218
433
|
var ATTR_INVOCATION_ID = "azure.ai.agentserver.invocations.id";
|
|
219
434
|
var ATTR_INVOCATIONS_ERROR_CODE = "azure.ai.agentserver.invocations.error.code";
|
|
220
435
|
var ATTR_INVOCATIONS_ERROR_MESSAGE = "azure.ai.agentserver.invocations.error.message";
|
|
436
|
+
var ATTR_RESPONSES_STREAMING = "azure.ai.agentserver.responses.streaming";
|
|
437
|
+
var ATTR_RESPONSES_ERROR_CODE = "azure.ai.agentserver.responses.error.code";
|
|
438
|
+
var ATTR_RESPONSES_ERROR_MESSAGE = "azure.ai.agentserver.responses.error.message";
|
|
221
439
|
var SERVICE_NAME_VALUE = "azure.ai.agentserver";
|
|
222
440
|
var GEN_AI_SYSTEM_VALUE = "azure.ai.agentserver";
|
|
223
441
|
var GEN_AI_PROVIDER_NAME_VALUE = "AzureAI Hosted Agents";
|
|
@@ -250,6 +468,11 @@ function startAgentServerRequestSpan(options) {
|
|
|
250
468
|
setAttribute(attributes, ATTR_FOUNDRY_PROJECT_ID, config.projectArmId);
|
|
251
469
|
setAttribute(attributes, ATTR_SESSION_ID, options.sessionId);
|
|
252
470
|
setAttribute(attributes, ATTR_INVOCATION_ID, options.invocationId);
|
|
471
|
+
setAttribute(attributes, ATTR_GEN_AI_RESPONSE_ID, options.responseId);
|
|
472
|
+
setAttribute(attributes, ATTR_GEN_AI_CONVERSATION_ID, options.conversationId);
|
|
473
|
+
if (options.streaming !== void 0) {
|
|
474
|
+
attributes[ATTR_RESPONSES_STREAMING] = typeof options.streaming === "boolean" ? options.streaming : options.streaming === "true";
|
|
475
|
+
}
|
|
253
476
|
const requestId = options.headers("x-request-id") ?? options.correlationRequestId;
|
|
254
477
|
setAttribute(attributes, "x_request_id", requestId);
|
|
255
478
|
const span = trace.getTracer(instrumentationScope).startSpan(
|
|
@@ -265,6 +488,9 @@ function startAgentServerRequestSpan(options) {
|
|
|
265
488
|
{
|
|
266
489
|
invocationId: options.invocationId,
|
|
267
490
|
sessionId: options.sessionId,
|
|
491
|
+
responseId: options.responseId,
|
|
492
|
+
conversationId: options.conversationId,
|
|
493
|
+
streaming: options.streaming,
|
|
268
494
|
requestId
|
|
269
495
|
}
|
|
270
496
|
);
|
|
@@ -314,10 +540,28 @@ function withAgentServerBaggage(sourceContext, values) {
|
|
|
314
540
|
});
|
|
315
541
|
}
|
|
316
542
|
if (values.requestId) {
|
|
543
|
+
baggage = baggage.setEntry(AGENTSERVER_REQUEST_ID_BAGGAGE, {
|
|
544
|
+
value: values.requestId
|
|
545
|
+
});
|
|
317
546
|
baggage = baggage.setEntry("x_request_id", {
|
|
318
547
|
value: values.requestId
|
|
319
548
|
});
|
|
320
549
|
}
|
|
550
|
+
if (values.responseId !== void 0) {
|
|
551
|
+
baggage = baggage.setEntry(AGENTSERVER_RESPONSE_ID_BAGGAGE, {
|
|
552
|
+
value: values.responseId
|
|
553
|
+
});
|
|
554
|
+
}
|
|
555
|
+
if (values.conversationId !== void 0) {
|
|
556
|
+
baggage = baggage.setEntry(AGENTSERVER_CONVERSATION_ID_BAGGAGE, {
|
|
557
|
+
value: values.conversationId
|
|
558
|
+
});
|
|
559
|
+
}
|
|
560
|
+
if (values.streaming !== void 0) {
|
|
561
|
+
baggage = baggage.setEntry(AGENTSERVER_STREAMING_BAGGAGE, {
|
|
562
|
+
value: typeof values.streaming === "boolean" ? String(values.streaming) : values.streaming
|
|
563
|
+
});
|
|
564
|
+
}
|
|
321
565
|
return propagation.setBaggage(sourceContext, baggage);
|
|
322
566
|
}
|
|
323
567
|
function recordAgentServerSpanError(span, error, code = "internal_error") {
|
|
@@ -450,7 +694,7 @@ async function configureAgentServerObservabilityOnce(options) {
|
|
|
450
694
|
return makeHandle(exporters, shutdownCallbacks);
|
|
451
695
|
} catch (error) {
|
|
452
696
|
logger.warn("Failed to initialize Azure Monitor observability", {
|
|
453
|
-
error:
|
|
697
|
+
error: formatError2(error)
|
|
454
698
|
});
|
|
455
699
|
}
|
|
456
700
|
}
|
|
@@ -469,7 +713,7 @@ async function configureAgentServerObservabilityOnce(options) {
|
|
|
469
713
|
return makeHandle(exporters, shutdownCallbacks, setup2.configured);
|
|
470
714
|
} catch (error) {
|
|
471
715
|
logger.warn("Failed to initialize OTLP observability", {
|
|
472
|
-
error:
|
|
716
|
+
error: formatError2(error)
|
|
473
717
|
});
|
|
474
718
|
}
|
|
475
719
|
}
|
|
@@ -498,7 +742,7 @@ async function createOtlpSpanProcessor(config, logger) {
|
|
|
498
742
|
return new BatchSpanProcessor(new OTLPTraceExporter({ url: endpoint }));
|
|
499
743
|
} catch (error) {
|
|
500
744
|
logger.warn("Failed to initialize OTLP span processor", {
|
|
501
|
-
error:
|
|
745
|
+
error: formatError2(error)
|
|
502
746
|
});
|
|
503
747
|
return void 0;
|
|
504
748
|
}
|
|
@@ -520,7 +764,7 @@ async function createResource(config, logger) {
|
|
|
520
764
|
return resources.resourceFromAttributes(attributes);
|
|
521
765
|
} catch (error) {
|
|
522
766
|
logger.debug?.("OpenTelemetry resource package unavailable", {
|
|
523
|
-
error:
|
|
767
|
+
error: formatError2(error)
|
|
524
768
|
});
|
|
525
769
|
return void 0;
|
|
526
770
|
}
|
|
@@ -546,7 +790,7 @@ async function ensureTraceProvider(config, logger, spanProcessors) {
|
|
|
546
790
|
}
|
|
547
791
|
} catch (error) {
|
|
548
792
|
logger.debug?.("OpenTelemetry API provider inspection failed", {
|
|
549
|
-
error:
|
|
793
|
+
error: formatError2(error)
|
|
550
794
|
});
|
|
551
795
|
}
|
|
552
796
|
try {
|
|
@@ -564,19 +808,15 @@ async function ensureTraceProvider(config, logger, spanProcessors) {
|
|
|
564
808
|
};
|
|
565
809
|
} catch (error) {
|
|
566
810
|
logger.warn("Failed to initialize local OpenTelemetry tracer provider", {
|
|
567
|
-
error:
|
|
811
|
+
error: formatError2(error)
|
|
568
812
|
});
|
|
569
813
|
return { configured: false };
|
|
570
814
|
}
|
|
571
815
|
}
|
|
572
816
|
async function importOptional(specifier) {
|
|
573
|
-
|
|
574
|
-
"specifier",
|
|
575
|
-
"return import(specifier)"
|
|
576
|
-
);
|
|
577
|
-
return await dynamicImport(specifier);
|
|
817
|
+
return await import(specifier);
|
|
578
818
|
}
|
|
579
|
-
function
|
|
819
|
+
function formatError2(error) {
|
|
580
820
|
if (error instanceof Error) {
|
|
581
821
|
return error.stack ?? error.message;
|
|
582
822
|
}
|
|
@@ -627,7 +867,10 @@ export {
|
|
|
627
867
|
AGENTSERVER_CONVERSATION_ID_BAGGAGE,
|
|
628
868
|
AGENTSERVER_CORE_PACKAGE_VERSION,
|
|
629
869
|
AGENTSERVER_INVOCATION_ID_BAGGAGE,
|
|
870
|
+
AGENTSERVER_REQUEST_ID_BAGGAGE,
|
|
871
|
+
AGENTSERVER_RESPONSE_ID_BAGGAGE,
|
|
630
872
|
AGENTSERVER_SESSION_ID_BAGGAGE,
|
|
873
|
+
AGENTSERVER_STREAMING_BAGGAGE,
|
|
631
874
|
ATTR_FOUNDRY_PROJECT_ID,
|
|
632
875
|
ATTR_GEN_AI_AGENT_ID,
|
|
633
876
|
ATTR_GEN_AI_AGENT_NAME,
|
|
@@ -640,22 +883,42 @@ export {
|
|
|
640
883
|
ATTR_INVOCATIONS_ERROR_CODE,
|
|
641
884
|
ATTR_INVOCATIONS_ERROR_MESSAGE,
|
|
642
885
|
ATTR_INVOCATION_ID,
|
|
886
|
+
ATTR_RESPONSES_ERROR_CODE,
|
|
887
|
+
ATTR_RESPONSES_ERROR_MESSAGE,
|
|
888
|
+
ATTR_RESPONSES_STREAMING,
|
|
643
889
|
ATTR_SERVICE_NAME,
|
|
644
890
|
ATTR_SESSION_ID,
|
|
645
891
|
CHAT_ISOLATION_HEADER,
|
|
892
|
+
CLIENT_HEADER_PREFIX,
|
|
646
893
|
INVOCATION_ID_HEADER,
|
|
647
894
|
INVOCATION_ID_HEADERS,
|
|
648
895
|
REQUEST_ID_HEADER,
|
|
649
896
|
SESSION_ID_HEADER,
|
|
650
897
|
SESSION_ID_HEADERS,
|
|
651
898
|
USER_ISOLATION_HEADER,
|
|
899
|
+
addProtectedHeaders,
|
|
900
|
+
agentServerLoggingMiddleware,
|
|
901
|
+
agentServerRequestHeadersMiddleware,
|
|
652
902
|
buildPlatformServerHeader,
|
|
653
903
|
buildServerVersionSegment,
|
|
904
|
+
closeServerWithTimeout,
|
|
905
|
+
collectClientHeaders,
|
|
654
906
|
configureAgentServerObservability,
|
|
907
|
+
configureExpressAgentServerApp,
|
|
655
908
|
createErrorBody,
|
|
656
909
|
createFoundryEnrichmentSpanProcessor,
|
|
910
|
+
defaultAgentServerLogger,
|
|
911
|
+
errorMiddleware,
|
|
657
912
|
flushSpans,
|
|
913
|
+
formatError,
|
|
914
|
+
healthzHandler,
|
|
915
|
+
logAgentServerStartupConfiguration,
|
|
916
|
+
maskUri,
|
|
917
|
+
methodNotAllowed,
|
|
918
|
+
notFoundMiddleware,
|
|
919
|
+
readinessHandler,
|
|
658
920
|
recordAgentServerSpanError,
|
|
921
|
+
resolveAgentIsolation,
|
|
659
922
|
resolveAgentServerConfig,
|
|
660
923
|
resolveGracefulShutdownTimeout,
|
|
661
924
|
resolveInvocationIdentity,
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@cuylabs/agent-foundry-agentserver-core",
|
|
3
|
-
"version": "4.
|
|
3
|
+
"version": "4.9.0",
|
|
4
4
|
"description": "Shared TypeScript host utilities for Foundry Agent Server protocol packages",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./dist/index.js",
|
|
@@ -18,7 +18,8 @@
|
|
|
18
18
|
],
|
|
19
19
|
"dependencies": {
|
|
20
20
|
"@opentelemetry/api": "^1.9.0",
|
|
21
|
-
"@opentelemetry/context-async-hooks": "^2.6.0"
|
|
21
|
+
"@opentelemetry/context-async-hooks": "^2.6.0",
|
|
22
|
+
"express": "^5.0.0"
|
|
22
23
|
},
|
|
23
24
|
"optionalDependencies": {
|
|
24
25
|
"@azure/monitor-opentelemetry": "^1.16.0",
|
|
@@ -28,6 +29,7 @@
|
|
|
28
29
|
"@opentelemetry/sdk-trace-node": "^2.6.0"
|
|
29
30
|
},
|
|
30
31
|
"devDependencies": {
|
|
32
|
+
"@types/express": "^5.0.0",
|
|
31
33
|
"@types/node": "^22.0.0",
|
|
32
34
|
"tsup": "^8.0.0",
|
|
33
35
|
"typescript": "^5.7.0",
|