@rcrsr/rill-agent-foundry 0.18.4 → 0.18.6

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.
Files changed (46) hide show
  1. package/README.md +1 -1
  2. package/dist/index.d.ts +398 -17
  3. package/dist/index.js +969 -22
  4. package/package.json +15 -13
  5. package/dist/conversations.d.ts +0 -23
  6. package/dist/conversations.d.ts.map +0 -1
  7. package/dist/conversations.js +0 -91
  8. package/dist/conversations.js.map +0 -1
  9. package/dist/errors.d.ts +0 -26
  10. package/dist/errors.d.ts.map +0 -1
  11. package/dist/errors.js +0 -40
  12. package/dist/errors.js.map +0 -1
  13. package/dist/extract.d.ts +0 -19
  14. package/dist/extract.d.ts.map +0 -1
  15. package/dist/extract.js +0 -109
  16. package/dist/extract.js.map +0 -1
  17. package/dist/harness.d.ts +0 -21
  18. package/dist/harness.d.ts.map +0 -1
  19. package/dist/harness.js +0 -481
  20. package/dist/harness.js.map +0 -1
  21. package/dist/id.d.ts +0 -26
  22. package/dist/id.d.ts.map +0 -1
  23. package/dist/id.js +0 -83
  24. package/dist/id.js.map +0 -1
  25. package/dist/index.d.ts.map +0 -1
  26. package/dist/index.js.map +0 -1
  27. package/dist/response.d.ts +0 -25
  28. package/dist/response.d.ts.map +0 -1
  29. package/dist/response.js +0 -154
  30. package/dist/response.js.map +0 -1
  31. package/dist/session.d.ts +0 -24
  32. package/dist/session.d.ts.map +0 -1
  33. package/dist/session.js +0 -34
  34. package/dist/session.js.map +0 -1
  35. package/dist/stream.d.ts +0 -47
  36. package/dist/stream.d.ts.map +0 -1
  37. package/dist/stream.js +0 -189
  38. package/dist/stream.js.map +0 -1
  39. package/dist/telemetry.d.ts +0 -21
  40. package/dist/telemetry.d.ts.map +0 -1
  41. package/dist/telemetry.js +0 -53
  42. package/dist/telemetry.js.map +0 -1
  43. package/dist/types.d.ts +0 -161
  44. package/dist/types.d.ts.map +0 -1
  45. package/dist/types.js +0 -5
  46. package/dist/types.js.map +0 -1
package/dist/index.js CHANGED
@@ -1,22 +1,969 @@
1
- // ============================================================
2
- // TYPES
3
- // ============================================================
4
- // ============================================================
5
- // ERRORS
6
- // ============================================================
7
- export { CapacityError, CredentialError, InputError } from './errors.js';
8
- export { createIdGenerator, generateId } from './id.js';
9
- export { extractInput } from './extract.js';
10
- export { createSessionManager } from './session.js';
11
- // ============================================================
12
- // RESPONSE BUILDERS
13
- // ============================================================
14
- export { buildErrorResponse, buildSyncResponse, generateToolDefinitions, } from './response.js';
15
- // ============================================================
16
- // SSE STREAM EMITTER
17
- // ============================================================
18
- export { streamFoundryResponse } from './stream.js';
19
- export { createConversationsClient, PersistenceError, } from './conversations.js';
20
- export { initTelemetry, getTracer, shutdownTelemetry } from './telemetry.js';
21
- export { createFoundryHarness } from './harness.js';
22
- //# sourceMappingURL=index.js.map
1
+ // src/errors.ts
2
+ var InputError = class extends Error {
3
+ /** HTTP status code for this error. */
4
+ statusCode;
5
+ constructor(message) {
6
+ super(message);
7
+ this.name = "InputError";
8
+ this.statusCode = 400;
9
+ }
10
+ };
11
+ var CredentialError = class extends Error {
12
+ constructor(message) {
13
+ super(message);
14
+ this.name = "CredentialError";
15
+ }
16
+ };
17
+ var CapacityError = class extends Error {
18
+ /** HTTP status code for this error. */
19
+ statusCode;
20
+ constructor(max) {
21
+ super(`Maximum concurrent sessions (${max}) reached`);
22
+ this.name = "CapacityError";
23
+ this.statusCode = 429;
24
+ }
25
+ };
26
+
27
+ // src/id.ts
28
+ import { randomBytes } from "crypto";
29
+ var PARTITION_KEY_LENGTH = 18;
30
+ var ENTROPY_LENGTH = 32;
31
+ function generateEntropy(length) {
32
+ let result = "";
33
+ while (result.length < length) {
34
+ const bytes = randomBytes(Math.ceil(length * 1.5));
35
+ result += bytes.toString("base64").replace(/[^A-Za-z0-9]/g, "");
36
+ }
37
+ return result.slice(0, length);
38
+ }
39
+ function extractPartitionKey(id) {
40
+ const underscoreIdx = id.indexOf("_");
41
+ if (underscoreIdx === -1) return null;
42
+ const segment = id.slice(underscoreIdx + 1);
43
+ if (segment.length < PARTITION_KEY_LENGTH) return null;
44
+ return segment.slice(0, PARTITION_KEY_LENGTH);
45
+ }
46
+ function buildId(prefix, partitionKey) {
47
+ const entropy = generateEntropy(ENTROPY_LENGTH);
48
+ return `${prefix}_${partitionKey}${entropy}`;
49
+ }
50
+ function createIdGenerator(responseId, conversationId) {
51
+ const partitionKey = (conversationId !== void 0 ? extractPartitionKey(conversationId) : null) ?? (responseId !== void 0 ? extractPartitionKey(responseId) : null) ?? generateEntropy(PARTITION_KEY_LENGTH);
52
+ const resolvedResponseId = responseId ?? buildId("resp", partitionKey);
53
+ return {
54
+ responseId: resolvedResponseId,
55
+ generateMessageId: () => buildId("msg", partitionKey),
56
+ generateFunctionCallId: () => buildId("func", partitionKey),
57
+ generateFunctionOutputId: () => buildId("funcout", partitionKey)
58
+ };
59
+ }
60
+ function generateId(prefix) {
61
+ return prefix + generateEntropy(ENTROPY_LENGTH);
62
+ }
63
+
64
+ // src/extract.ts
65
+ function concatenateTextParts(parts) {
66
+ return parts.filter((p) => p.type === "input_text").map((p) => p.text).join("");
67
+ }
68
+ function extractUserText(content) {
69
+ if (typeof content === "string") {
70
+ return content;
71
+ }
72
+ return concatenateTextParts(content);
73
+ }
74
+ function findLastUserText(items) {
75
+ let text;
76
+ for (const item of items) {
77
+ if (item.type === "message" && item.role === "user") {
78
+ text = extractUserText(item.content);
79
+ }
80
+ }
81
+ return text;
82
+ }
83
+ function extractFunctionCallOutputs(items) {
84
+ const callMap = /* @__PURE__ */ new Map();
85
+ for (const item of items) {
86
+ if (item.type === "function_call") {
87
+ callMap.set(item.call_id, item.name);
88
+ }
89
+ }
90
+ const outputs = items.filter(
91
+ (item) => item.type === "function_call_output"
92
+ );
93
+ let targetAgent;
94
+ const toolResults = [];
95
+ for (const output of outputs) {
96
+ const handlerName = callMap.get(output.call_id);
97
+ if (handlerName === void 0) {
98
+ throw new InputError("function_call_output missing paired function_call");
99
+ }
100
+ if (targetAgent === void 0) {
101
+ targetAgent = handlerName;
102
+ }
103
+ toolResults.push({ call_id: output.call_id, output: output.output });
104
+ }
105
+ return {
106
+ params: { tool_results: toolResults },
107
+ targetAgent
108
+ };
109
+ }
110
+ function extractInput(input) {
111
+ if (input === void 0 || input === null || input === "") {
112
+ throw new InputError("Missing required field: input");
113
+ }
114
+ if (typeof input === "string") {
115
+ if (input.trim() === "") {
116
+ throw new InputError("Missing required field: input");
117
+ }
118
+ return { params: { input } };
119
+ }
120
+ if (!Array.isArray(input) || input.length === 0) {
121
+ throw new InputError("Missing required field: input");
122
+ }
123
+ const items = input;
124
+ const hasFunctionCallOutput = items.some(
125
+ (item) => item.type === "function_call_output"
126
+ );
127
+ if (hasFunctionCallOutput) {
128
+ return extractFunctionCallOutputs(items);
129
+ }
130
+ const userText = findLastUserText(items);
131
+ if (userText !== void 0) {
132
+ return { params: { input: userText } };
133
+ }
134
+ throw new InputError("No actionable input items found");
135
+ }
136
+
137
+ // src/session.ts
138
+ var DEFAULT_MAX_SESSIONS = 10;
139
+ function createSessionManager() {
140
+ const raw = process.env["MAX_CONCURRENT_SESSIONS"];
141
+ const parsed = raw !== void 0 ? parseInt(raw, 10) : NaN;
142
+ const max = Number.isFinite(parsed) && parsed > 0 ? parsed : DEFAULT_MAX_SESSIONS;
143
+ const active = /* @__PURE__ */ new Set();
144
+ return {
145
+ acquire(conversationId) {
146
+ if (active.size >= max) {
147
+ throw new CapacityError(max);
148
+ }
149
+ const sessionId = conversationId ?? generateId("sess_");
150
+ active.add(sessionId);
151
+ return sessionId;
152
+ },
153
+ release(sessionId) {
154
+ active.delete(sessionId);
155
+ },
156
+ activeCount() {
157
+ return active.size;
158
+ }
159
+ };
160
+ }
161
+
162
+ // src/response.ts
163
+ function coerceResult(result) {
164
+ if (result === null || result === void 0) {
165
+ return "";
166
+ }
167
+ if (typeof result === "string") {
168
+ return result;
169
+ }
170
+ if (typeof result === "number" || typeof result === "boolean") {
171
+ return String(result);
172
+ }
173
+ return JSON.stringify(result);
174
+ }
175
+ function buildSyncResponse(result, responseId) {
176
+ const status = result.state === "completed" ? "completed" : "failed";
177
+ const text = coerceResult(result.result);
178
+ const msgId = generateId("msg_");
179
+ return {
180
+ id: responseId,
181
+ object: "response",
182
+ created_at: Math.floor(Date.now() / 1e3),
183
+ status,
184
+ output: [
185
+ {
186
+ id: msgId,
187
+ type: "message",
188
+ role: "assistant",
189
+ status: "completed",
190
+ content: [
191
+ {
192
+ type: "text",
193
+ text,
194
+ annotations: []
195
+ }
196
+ ]
197
+ }
198
+ ],
199
+ error: status === "failed" ? { code: "SERVER_ERROR", message: text } : null,
200
+ metadata: {},
201
+ temperature: 0,
202
+ top_p: 0,
203
+ user: ""
204
+ };
205
+ }
206
+ var GENERIC_MESSAGES = {
207
+ INVALID_REQUEST: "Invalid request",
208
+ NOT_FOUND: "Not found",
209
+ RATE_LIMITED: "Rate limited",
210
+ SERVER_ERROR: "Internal server error"
211
+ };
212
+ var FALLBACK_MESSAGE = "An error occurred";
213
+ function buildErrorResponse(code, message, debug) {
214
+ const safeMessage = debug ? message : GENERIC_MESSAGES[code] ?? FALLBACK_MESSAGE;
215
+ return {
216
+ error: { code, message: safeMessage }
217
+ };
218
+ }
219
+ function mapParamType(rillType) {
220
+ switch (rillType) {
221
+ case "number":
222
+ case "integer":
223
+ return rillType;
224
+ case "boolean":
225
+ return "boolean";
226
+ default:
227
+ return "string";
228
+ }
229
+ }
230
+ function generateToolDefinitions(router) {
231
+ const defaultAgent = router.defaultAgent();
232
+ const description = router.describe(defaultAgent);
233
+ if (description === null) {
234
+ return [];
235
+ }
236
+ const properties = {};
237
+ const required = [];
238
+ for (const param of description.params) {
239
+ const property = {
240
+ type: mapParamType(param.type),
241
+ ...param.description !== void 0 ? { description: param.description } : {},
242
+ ...param.defaultValue !== void 0 ? { default: param.defaultValue } : {}
243
+ };
244
+ properties[param.name] = property;
245
+ if (param.required) {
246
+ required.push(param.name);
247
+ }
248
+ }
249
+ const parameters = {
250
+ type: "object",
251
+ properties,
252
+ ...required.length > 0 ? { required } : {}
253
+ };
254
+ return [
255
+ {
256
+ type: "function",
257
+ name: description.name,
258
+ description: description.description ?? "",
259
+ parameters,
260
+ strict: true
261
+ }
262
+ ];
263
+ }
264
+
265
+ // src/stream.ts
266
+ var REDACTED_ERROR_MESSAGE = "Internal server error";
267
+ var encoder = new TextEncoder();
268
+ function sseChunk(event, data) {
269
+ return `event: ${event}
270
+ data: ${data}
271
+
272
+ `;
273
+ }
274
+ function sseHeaders(options) {
275
+ const headers = {
276
+ "content-type": "text/event-stream; charset=utf-8",
277
+ "cache-control": "no-store",
278
+ "x-accel-buffering": "no",
279
+ "x-aml-foundry-agents-metadata": options?.metadataHeader ?? JSON.stringify({
280
+ package: { name: "azure-ai-agentserver-core", version: "1.0.0b17" },
281
+ runtime: {
282
+ python_version: "3.11.0",
283
+ platform: "Linux",
284
+ host_name: "",
285
+ replica_name: ""
286
+ }
287
+ })
288
+ };
289
+ if (options?.sessionId !== void 0) {
290
+ headers["x-agent-session-id"] = options.sessionId;
291
+ }
292
+ if (options?.invocationId !== void 0) {
293
+ headers["x-agent-invocation-id"] = options.invocationId;
294
+ }
295
+ return headers;
296
+ }
297
+ function createFoundryStreamResponse(responseId, options) {
298
+ let seq = 0;
299
+ let closed = false;
300
+ let keepAliveTimer;
301
+ const clearKeepAlive = () => {
302
+ if (keepAliveTimer !== void 0) {
303
+ clearInterval(keepAliveTimer);
304
+ keepAliveTimer = void 0;
305
+ }
306
+ };
307
+ function ev(event, payload) {
308
+ payload["sequence_number"] = seq++;
309
+ return encoder.encode(sseChunk(event, JSON.stringify(payload)));
310
+ }
311
+ function emitDeltas(controller, fullText) {
312
+ const tokens = fullText.split(" ");
313
+ for (let i = 0; i < tokens.length; i++) {
314
+ const piece = i === tokens.length - 1 ? tokens[i] : tokens[i] + " ";
315
+ controller.enqueue(
316
+ ev("response.output_text.delta", {
317
+ type: "response.output_text.delta",
318
+ delta: piece
319
+ })
320
+ );
321
+ }
322
+ }
323
+ function emitCompletion(controller, fullText) {
324
+ controller.enqueue(
325
+ ev("response.output_text.done", {
326
+ type: "response.output_text.done",
327
+ text: fullText
328
+ })
329
+ );
330
+ controller.enqueue(
331
+ ev("response.completed", {
332
+ type: "response.completed",
333
+ response: {
334
+ object: "response",
335
+ id: responseId,
336
+ status: "completed",
337
+ created_at: Math.floor(Date.now() / 1e3),
338
+ output: []
339
+ }
340
+ })
341
+ );
342
+ closed = true;
343
+ controller.close();
344
+ }
345
+ function emitError(controller, err) {
346
+ clearKeepAlive();
347
+ options.onError?.(err);
348
+ const rawMessage = err instanceof Error ? err.message : String(err);
349
+ const message = options.debugErrors === true ? rawMessage : REDACTED_ERROR_MESSAGE;
350
+ controller.enqueue(
351
+ encoder.encode(
352
+ sseChunk(
353
+ "error",
354
+ JSON.stringify({
355
+ type: "error",
356
+ sequence_number: seq++,
357
+ code: "SERVER_ERROR",
358
+ message,
359
+ param: ""
360
+ })
361
+ )
362
+ )
363
+ );
364
+ closed = true;
365
+ controller.close();
366
+ }
367
+ const body = new ReadableStream({
368
+ start(controller) {
369
+ keepAliveTimer = setInterval(() => {
370
+ if (!closed) {
371
+ controller.enqueue(encoder.encode(": keep-alive\n\n"));
372
+ }
373
+ }, 15e3);
374
+ if (options.chunks !== void 0) {
375
+ (async () => {
376
+ let fullText = "";
377
+ for await (const chunk of options.chunks) {
378
+ if (closed) break;
379
+ fullText += chunk;
380
+ controller.enqueue(
381
+ ev("response.output_text.delta", {
382
+ type: "response.output_text.delta",
383
+ delta: chunk
384
+ })
385
+ );
386
+ }
387
+ if (closed) return;
388
+ clearKeepAlive();
389
+ emitCompletion(controller, fullText);
390
+ })().catch((err) => emitError(controller, err));
391
+ } else if (options.resultPromise !== void 0) {
392
+ options.resultPromise.then((resultText) => {
393
+ if (closed) return;
394
+ clearKeepAlive();
395
+ emitDeltas(controller, resultText);
396
+ emitCompletion(controller, resultText);
397
+ }).catch((err) => emitError(controller, err));
398
+ } else {
399
+ clearKeepAlive();
400
+ emitCompletion(controller, "");
401
+ }
402
+ },
403
+ cancel() {
404
+ closed = true;
405
+ clearKeepAlive();
406
+ }
407
+ });
408
+ return new Response(body, {
409
+ status: 200,
410
+ headers: sseHeaders(options)
411
+ });
412
+ }
413
+ function streamFoundryResponse(_c, responseId, resultStream, options) {
414
+ const resultPromise = options.resultPromise ?? (async () => {
415
+ let text = "";
416
+ for await (const chunk of resultStream) {
417
+ if (chunk.value !== null && chunk.value !== void 0) {
418
+ text += typeof chunk.value === "string" ? chunk.value : JSON.stringify(chunk.value);
419
+ }
420
+ }
421
+ return text;
422
+ })();
423
+ return createFoundryStreamResponse(responseId, {
424
+ ...options,
425
+ resultPromise
426
+ });
427
+ }
428
+
429
+ // src/conversations.ts
430
+ var PersistenceError = class extends Error {
431
+ statusCode;
432
+ constructor(message) {
433
+ super(message);
434
+ this.name = "PersistenceError";
435
+ this.statusCode = 502;
436
+ }
437
+ };
438
+ var API_VERSION = "2025-11-15-preview";
439
+ var TOKEN_SCOPE = "https://ai.azure.com/.default";
440
+ var REQUEST_TIMEOUT_MS = 3e4;
441
+ function createConversationsClient(projectEndpoint, credential) {
442
+ return {
443
+ async saveItems(conversationId, items) {
444
+ let token;
445
+ try {
446
+ const accessToken = await credential.getToken(TOKEN_SCOPE);
447
+ if (accessToken === null) {
448
+ throw new Error(`Failed to acquire token for scope ${TOKEN_SCOPE}`);
449
+ }
450
+ token = accessToken.token;
451
+ } catch (err) {
452
+ if (err instanceof CredentialError) {
453
+ throw err;
454
+ }
455
+ const message = err instanceof Error ? err.message : String(err);
456
+ throw new CredentialError(message);
457
+ }
458
+ const encodedConversationId = encodeURIComponent(conversationId);
459
+ const url = new URL(
460
+ `${projectEndpoint}/openai/conversations/${encodedConversationId}/items`
461
+ );
462
+ url.searchParams.set("api-version", API_VERSION);
463
+ const controller = new AbortController();
464
+ const timer = setTimeout(() => controller.abort(), REQUEST_TIMEOUT_MS);
465
+ try {
466
+ let response;
467
+ try {
468
+ response = await fetch(url.toString(), {
469
+ method: "POST",
470
+ headers: {
471
+ "Content-Type": "application/json",
472
+ Authorization: `Bearer ${token}`
473
+ },
474
+ body: JSON.stringify({ items }),
475
+ signal: controller.signal
476
+ });
477
+ } catch (err) {
478
+ const isTimeout = err instanceof Error && err.name === "AbortError";
479
+ throw new PersistenceError(
480
+ isTimeout ? "Conversations API timeout" : `Conversations API error: ${err instanceof Error ? err.message : String(err)}`
481
+ );
482
+ }
483
+ if (!response.ok) {
484
+ throw new PersistenceError(
485
+ `Conversations API error: ${response.status}`
486
+ );
487
+ }
488
+ } finally {
489
+ clearTimeout(timer);
490
+ }
491
+ }
492
+ };
493
+ }
494
+
495
+ // src/telemetry.ts
496
+ import { trace } from "@opentelemetry/api";
497
+ import { NodeSDK } from "@opentelemetry/sdk-node";
498
+ import { OTLPTraceExporter } from "@opentelemetry/exporter-trace-otlp-http";
499
+ import { Resource } from "@opentelemetry/resources";
500
+ var sdk;
501
+ function initTelemetry(options) {
502
+ if (!process.env["OTEL_EXPORTER_OTLP_ENDPOINT"]) return;
503
+ if (sdk !== void 0) return;
504
+ const resource = new Resource({
505
+ "service.name": options?.agentName ?? "rill-foundry-harness",
506
+ "service.version": options?.agentVersion ?? "0.0.0"
507
+ });
508
+ const traceExporter = new OTLPTraceExporter();
509
+ sdk = new NodeSDK({ resource, traceExporter });
510
+ sdk.start();
511
+ }
512
+ function getTracer() {
513
+ return trace.getTracer("rill-foundry-harness");
514
+ }
515
+ async function shutdownTelemetry() {
516
+ if (sdk === void 0) return;
517
+ const instance = sdk;
518
+ sdk = void 0;
519
+ await instance.shutdown();
520
+ }
521
+
522
+ // src/harness.ts
523
+ import "hono";
524
+ import { SpanStatusCode } from "@opentelemetry/api";
525
+ import { DefaultAzureCredential } from "@azure/identity";
526
+ import { validateParams, routerErrorToStatus } from "@rcrsr/rill-agent";
527
+
528
+ // ../../shared/hono-kit/src/index.ts
529
+ import { Hono } from "hono";
530
+ import { serve } from "@hono/node-server";
531
+ function assertJsonObject(parsed) {
532
+ if (parsed === null || typeof parsed !== "object" || Array.isArray(parsed)) {
533
+ throw new Error("Request body must be a JSON object");
534
+ }
535
+ return parsed;
536
+ }
537
+ function createHarnessLifecycle(options) {
538
+ const app = new Hono();
539
+ let server;
540
+ async function listen(port) {
541
+ if (server !== void 0) {
542
+ throw new Error("Server is already listening");
543
+ }
544
+ return new Promise((resolve) => {
545
+ server = serve({ fetch: app.fetch, port }, () => {
546
+ options?.serverTweaks?.(server);
547
+ resolve();
548
+ });
549
+ });
550
+ }
551
+ async function close() {
552
+ if (server !== void 0) {
553
+ server.close();
554
+ server = void 0;
555
+ }
556
+ }
557
+ return { app, listen, close };
558
+ }
559
+
560
+ // src/harness.ts
561
+ var DEFAULT_PORT = 8088;
562
+ function resolvePort(options) {
563
+ if (options?.port !== void 0) {
564
+ if (!Number.isFinite(options.port)) {
565
+ throw new Error(`Invalid port: "${options.port}"`);
566
+ }
567
+ return options.port;
568
+ }
569
+ const envVal = process.env["DEFAULT_AD_PORT"];
570
+ if (envVal === void 0) {
571
+ return DEFAULT_PORT;
572
+ }
573
+ const parsed = Number(envVal);
574
+ if (!Number.isFinite(parsed) || envVal.trim() === "") {
575
+ throw new Error(`Invalid port: "${envVal}"`);
576
+ }
577
+ return parsed;
578
+ }
579
+ function resolveConversationId(conversation) {
580
+ if (conversation === void 0) {
581
+ return void 0;
582
+ }
583
+ if (typeof conversation === "string") {
584
+ return conversation;
585
+ }
586
+ return conversation.id;
587
+ }
588
+ function createFoundryHarness(router, options) {
589
+ const port = resolvePort(options);
590
+ const debugErrors = options?.debugErrors ?? process.env["FOUNDRY_AGENT_DEBUG_ERRORS"] === "true";
591
+ const forceSync = options?.forceSync ?? process.env["FOUNDRY_AGENT_FORCE_SYNC"] === "true";
592
+ const agentName = options?.agentName ?? process.env["FOUNDRY_AGENT_NAME"];
593
+ const agentVersion = options?.agentVersion ?? process.env["FOUNDRY_AGENT_VERSION"];
594
+ initTelemetry({
595
+ agentName: agentName ?? router.defaultAgent(),
596
+ agentVersion
597
+ });
598
+ const sessions = createSessionManager();
599
+ const projectEndpoint = process.env["FOUNDRY_PROJECT_ENDPOINT"];
600
+ const azureCredential = projectEndpoint !== void 0 ? new DefaultAzureCredential() : void 0;
601
+ const conversationsClient = projectEndpoint !== void 0 && azureCredential !== void 0 ? createConversationsClient(projectEndpoint, azureCredential) : void 0;
602
+ let totalRequests = 0;
603
+ let errorCount = 0;
604
+ let ready = false;
605
+ function serverTweaks(srv) {
606
+ const s = srv;
607
+ s["requestTimeout"] = 0;
608
+ s["headersTimeout"] = 0;
609
+ s["keepAliveTimeout"] = 0;
610
+ s["timeout"] = 0;
611
+ }
612
+ const lifecycle = createHarnessLifecycle({ serverTweaks });
613
+ const app = lifecycle.app;
614
+ const metadataHeader = JSON.stringify({
615
+ package: {
616
+ name: "azure-ai-agentserver-core",
617
+ version: "1.0.0b17"
618
+ },
619
+ runtime: {
620
+ python_version: "3.11.0",
621
+ platform: "Linux",
622
+ host_name: "",
623
+ replica_name: ""
624
+ }
625
+ });
626
+ app.use("*", async (c, next) => {
627
+ await next();
628
+ c.res.headers.set("x-aml-foundry-agents-metadata", metadataHeader);
629
+ });
630
+ app.get("/liveness", (c) => {
631
+ totalRequests++;
632
+ return c.json({ status: "ok" }, 200);
633
+ });
634
+ app.get("/readiness", (c) => {
635
+ totalRequests++;
636
+ if (!ready) {
637
+ errorCount++;
638
+ return c.json({ status: "initializing" }, 503);
639
+ }
640
+ return c.json({ status: "ready" }, 200);
641
+ });
642
+ app.get("/metrics", (c) => {
643
+ totalRequests++;
644
+ return c.json({
645
+ activeSessions: sessions.activeCount(),
646
+ totalRequests,
647
+ errorCount
648
+ });
649
+ });
650
+ async function handleResponseRequest(c) {
651
+ totalRequests++;
652
+ let body;
653
+ try {
654
+ const parsed = await c.req.json();
655
+ body = assertJsonObject(parsed);
656
+ } catch (err) {
657
+ errorCount++;
658
+ return c.json(
659
+ buildErrorResponse(
660
+ "INVALID_REQUEST",
661
+ err instanceof Error ? err.message : "Invalid JSON in request body",
662
+ debugErrors
663
+ ),
664
+ 400
665
+ );
666
+ }
667
+ let extracted;
668
+ try {
669
+ extracted = extractInput(body.input);
670
+ } catch (err) {
671
+ errorCount++;
672
+ if (err instanceof InputError) {
673
+ return c.json(
674
+ buildErrorResponse("INVALID_REQUEST", err.message, debugErrors),
675
+ 400
676
+ );
677
+ }
678
+ return c.json(
679
+ buildErrorResponse(
680
+ "SERVER_ERROR",
681
+ err instanceof Error ? err.message : String(err),
682
+ debugErrors
683
+ ),
684
+ 500
685
+ );
686
+ }
687
+ const conversationId = resolveConversationId(body.conversation);
688
+ const agentName_ = extracted.targetAgent ?? router.defaultAgent();
689
+ const validationError = validateParams(
690
+ extracted.params,
691
+ agentName_,
692
+ router
693
+ );
694
+ if (validationError !== null) {
695
+ errorCount++;
696
+ return c.json(
697
+ buildErrorResponse("INVALID_REQUEST", validationError, debugErrors),
698
+ 400
699
+ );
700
+ }
701
+ let sessionId;
702
+ try {
703
+ sessionId = sessions.acquire(conversationId);
704
+ } catch (err) {
705
+ errorCount++;
706
+ if (err instanceof CapacityError) {
707
+ return c.json(
708
+ buildErrorResponse("RATE_LIMITED", err.message, debugErrors),
709
+ 429
710
+ );
711
+ }
712
+ return c.json(
713
+ buildErrorResponse(
714
+ "SERVER_ERROR",
715
+ err instanceof Error ? err.message : String(err),
716
+ debugErrors
717
+ ),
718
+ 500
719
+ );
720
+ }
721
+ const idGen = createIdGenerator(
722
+ body.metadata?.["response_id"],
723
+ conversationId
724
+ );
725
+ const responseId = idGen.responseId;
726
+ const invocationId = c.req.header("x-agent-invocation-id") ?? generateId("inv_");
727
+ const agentSessionId = c.req.query("session") ?? c.req.header("x-agent-session-id") ?? sessionId;
728
+ const sessionVars = {};
729
+ const oid = c.req.header("x-aml-oid");
730
+ if (oid !== void 0) sessionVars["AZURE_OID"] = oid;
731
+ const tid = c.req.header("x-aml-tid");
732
+ if (tid !== void 0) sessionVars["AZURE_TID"] = tid;
733
+ if (typeof body.user === "string") sessionVars["FOUNDRY_USER"] = body.user;
734
+ if (typeof body.model === "string")
735
+ sessionVars["FOUNDRY_MODEL"] = body.model;
736
+ if (typeof body.temperature === "number")
737
+ sessionVars["FOUNDRY_TEMPERATURE"] = String(body.temperature);
738
+ const runContext = {
739
+ sessionVars
740
+ };
741
+ const runRequest = {
742
+ params: extracted.params
743
+ };
744
+ if (body.stream === true && !forceSync) {
745
+ const tracer = getTracer();
746
+ const span = tracer.startSpan("foundry.agent.run", {
747
+ attributes: {
748
+ "foundry.agent.name": agentName_,
749
+ "foundry.response_id": responseId,
750
+ "foundry.stream": true
751
+ }
752
+ });
753
+ let pushChunk;
754
+ let endChunks;
755
+ let failChunks;
756
+ const chunkBuffer = [];
757
+ let done = false;
758
+ let waiting;
759
+ pushChunk = (chunk) => {
760
+ if (waiting !== void 0) {
761
+ const resolve = waiting;
762
+ waiting = void 0;
763
+ resolve({ value: chunk, done: false });
764
+ } else {
765
+ chunkBuffer.push(chunk);
766
+ }
767
+ };
768
+ endChunks = () => {
769
+ done = true;
770
+ if (waiting !== void 0) {
771
+ const resolve = waiting;
772
+ waiting = void 0;
773
+ resolve({ value: void 0, done: true });
774
+ }
775
+ };
776
+ let pendingError;
777
+ let rejectWaiting;
778
+ failChunks = (err) => {
779
+ pendingError = err;
780
+ done = true;
781
+ if (rejectWaiting !== void 0) {
782
+ const reject = rejectWaiting;
783
+ rejectWaiting = void 0;
784
+ waiting = void 0;
785
+ reject(err);
786
+ }
787
+ };
788
+ const chunks = {
789
+ [Symbol.asyncIterator]() {
790
+ return {
791
+ next() {
792
+ if (chunkBuffer.length > 0) {
793
+ return Promise.resolve({
794
+ value: chunkBuffer.shift(),
795
+ done: false
796
+ });
797
+ }
798
+ if (done) {
799
+ if (pendingError !== void 0) {
800
+ return Promise.reject(pendingError);
801
+ }
802
+ return Promise.resolve({
803
+ value: void 0,
804
+ done: true
805
+ });
806
+ }
807
+ return new Promise((resolve, reject) => {
808
+ waiting = resolve;
809
+ rejectWaiting = reject;
810
+ });
811
+ }
812
+ };
813
+ }
814
+ };
815
+ const onChunk = async (chunk) => {
816
+ const text = typeof chunk === "string" ? chunk : JSON.stringify(chunk);
817
+ pushChunk(text);
818
+ };
819
+ const streamContext = { ...runContext, onChunk };
820
+ router.run(agentName_, runRequest, streamContext).then((result) => {
821
+ span.setAttribute("foundry.agent.state", result.state);
822
+ span.end();
823
+ sessions.release(sessionId);
824
+ if (!result.streamed) {
825
+ if (result.result !== null && result.result !== void 0) {
826
+ const text = typeof result.result === "string" ? result.result : JSON.stringify(result.result);
827
+ if (text !== "") pushChunk(text);
828
+ }
829
+ }
830
+ endChunks();
831
+ }).catch((err) => {
832
+ span.setStatus({
833
+ code: SpanStatusCode.ERROR,
834
+ message: err instanceof Error ? err.message : String(err)
835
+ });
836
+ span.end();
837
+ sessions.release(sessionId);
838
+ failChunks(err);
839
+ });
840
+ return createFoundryStreamResponse(responseId, {
841
+ chunks,
842
+ idGenerator: idGen,
843
+ onError: (_err) => {
844
+ errorCount++;
845
+ },
846
+ sessionId: agentSessionId,
847
+ invocationId,
848
+ metadataHeader,
849
+ debugErrors
850
+ });
851
+ }
852
+ try {
853
+ let result;
854
+ const tracer = getTracer();
855
+ const span = tracer.startSpan("foundry.agent.run", {
856
+ attributes: {
857
+ "foundry.agent.name": agentName_,
858
+ "foundry.response_id": responseId,
859
+ "foundry.stream": false
860
+ }
861
+ });
862
+ try {
863
+ result = await router.run(agentName_, runRequest, runContext);
864
+ span.setAttribute("foundry.agent.state", result.state);
865
+ } catch (err) {
866
+ span.setStatus({
867
+ code: SpanStatusCode.ERROR,
868
+ message: err instanceof Error ? err.message : String(err)
869
+ });
870
+ span.end();
871
+ errorCount++;
872
+ const msg = err instanceof Error ? err.message : String(err);
873
+ const status = routerErrorToStatus(err);
874
+ if (status === 404) {
875
+ return c.json(buildErrorResponse("NOT_FOUND", msg, debugErrors), 404);
876
+ }
877
+ return c.json(
878
+ buildErrorResponse("SERVER_ERROR", msg, debugErrors),
879
+ 500
880
+ );
881
+ }
882
+ span.end();
883
+ const response = buildSyncResponse(result, responseId);
884
+ const shouldStore = body.store === true;
885
+ if (shouldStore && conversationId !== void 0 && conversationsClient !== void 0) {
886
+ try {
887
+ await conversationsClient.saveItems(conversationId, response.output);
888
+ } catch (err) {
889
+ errorCount++;
890
+ if (err instanceof PersistenceError) {
891
+ return c.json(
892
+ buildErrorResponse("SERVER_ERROR", err.message, debugErrors),
893
+ 502
894
+ );
895
+ }
896
+ return c.json(
897
+ buildErrorResponse(
898
+ "SERVER_ERROR",
899
+ err instanceof Error ? err.message : String(err),
900
+ debugErrors
901
+ ),
902
+ 502
903
+ );
904
+ }
905
+ }
906
+ if (result.state === "error") {
907
+ errorCount++;
908
+ }
909
+ return c.json(response, 200);
910
+ } finally {
911
+ sessions.release(sessionId);
912
+ }
913
+ }
914
+ app.post("/responses", (c) => handleResponseRequest(c));
915
+ app.post("/runs", (c) => handleResponseRequest(c));
916
+ ready = true;
917
+ async function listen() {
918
+ if (azureCredential !== void 0) {
919
+ try {
920
+ const token = await azureCredential.getToken(
921
+ "https://ai.azure.com/.default"
922
+ );
923
+ if (token === null) {
924
+ throw new CredentialError(
925
+ "Failed to acquire token for scope https://ai.azure.com/.default"
926
+ );
927
+ }
928
+ } catch (err) {
929
+ if (!(err instanceof CredentialError)) {
930
+ const message = err instanceof Error ? err.message : String(err);
931
+ throw new CredentialError(message);
932
+ }
933
+ process.exit(1);
934
+ }
935
+ }
936
+ await lifecycle.listen(port);
937
+ }
938
+ async function close() {
939
+ await lifecycle.close();
940
+ await shutdownTelemetry();
941
+ }
942
+ function metrics() {
943
+ return {
944
+ activeSessions: sessions.activeCount(),
945
+ totalRequests,
946
+ errorCount
947
+ };
948
+ }
949
+ return { listen, close, app, metrics };
950
+ }
951
+ export {
952
+ CapacityError,
953
+ CredentialError,
954
+ InputError,
955
+ PersistenceError,
956
+ buildErrorResponse,
957
+ buildSyncResponse,
958
+ createConversationsClient,
959
+ createFoundryHarness,
960
+ createIdGenerator,
961
+ createSessionManager,
962
+ extractInput,
963
+ generateId,
964
+ generateToolDefinitions,
965
+ getTracer,
966
+ initTelemetry,
967
+ shutdownTelemetry,
968
+ streamFoundryResponse
969
+ };