@rcrsr/rill-agent-foundry 0.20.0 → 0.21.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.
Files changed (3) hide show
  1. package/dist/index.d.ts +21 -30
  2. package/dist/index.js +294 -204
  3. package/package.json +10 -10
package/dist/index.d.ts CHANGED
@@ -250,12 +250,23 @@ export interface SessionManager {
250
250
  */
251
251
  activeCount(): number;
252
252
  }
253
+ /**
254
+ * Options accepted by createSessionManager.
255
+ */
256
+ export interface SessionManagerOptions {
257
+ /**
258
+ * Maximum number of concurrently open sessions. Overrides the
259
+ * MAX_CONCURRENT_SESSIONS env var when provided.
260
+ */
261
+ readonly maxConcurrentSessions?: number | undefined;
262
+ }
253
263
  /**
254
264
  * Create a bounded pool manager for concurrent rill sessions.
255
265
  *
256
- * Max capacity reads from MAX_CONCURRENT_SESSIONS env var, default 10.
266
+ * Max capacity resolution order: options.maxConcurrentSessions, then the
267
+ * MAX_CONCURRENT_SESSIONS env var, then DEFAULT_MAX_SESSIONS.
257
268
  */
258
- export declare function createSessionManager(): SessionManager;
269
+ export declare function createSessionManager(options?: SessionManagerOptions): SessionManager;
259
270
  export interface HandlerDescription {
260
271
  readonly name: string;
261
272
  readonly description?: string | undefined;
@@ -316,8 +327,15 @@ export interface AgentRouter {
316
327
  *
317
328
  * State mapping: 'completed' → 'completed', 'error' → 'failed'.
318
329
  * Result is coerced to string; errors are encoded in the response body.
330
+ *
331
+ * When `debugErrors` is false (default), a failed-state error message is
332
+ * redacted with the same generic message `buildErrorResponse` uses for
333
+ * SERVER_ERROR, so internal detail does not leak through the sync response
334
+ * path when it is suppressed everywhere else. The same redaction is applied
335
+ * to the output message `text`, since it otherwise carries the same raw
336
+ * failure detail. When true, the raw message is passed through verbatim.
319
337
  */
320
- export declare function buildSyncResponse(result: RunResponse, responseId: string): FoundryResponse;
338
+ export declare function buildSyncResponse(result: RunResponse, responseId: string, debugErrors?: boolean): FoundryResponse;
321
339
  /**
322
340
  * Build a non-streaming JSON error response body.
323
341
  *
@@ -333,33 +351,6 @@ export declare function buildErrorResponse(code: string, message: string, debug?
333
351
  * Returns an empty array when describe() returns null.
334
352
  */
335
353
  export declare function generateToolDefinitions(router: AgentRouter): FoundryToolDefinition[];
336
- export interface StreamOptions {
337
- readonly onError?: ((err: unknown) => void) | undefined;
338
- /** IdGenerator scoped to the request for correlated message IDs. */
339
- readonly idGenerator?: IdGenerator | undefined;
340
- /** Session ID echoed back in x-agent-session-id response header. */
341
- readonly sessionId?: string | undefined;
342
- /** Invocation ID echoed back in x-agent-invocation-id response header. */
343
- readonly invocationId?: string | undefined;
344
- /** Pre-built x-aml-foundry-agents-metadata JSON string. */
345
- readonly metadataHeader?: string | undefined;
346
- /**
347
- * When true, raw error messages are forwarded to clients. When false
348
- * (default), error events emit a generic message to avoid leaking
349
- * internal details. Mirrors the harness `debugErrors` option used by
350
- * `buildErrorResponse`.
351
- */
352
- readonly debugErrors?: boolean | undefined;
353
- }
354
- /**
355
- * Stream a Foundry Responses lifecycle via SSE.
356
- * Delegates to createFoundryStreamResponse for all paths.
357
- */
358
- export declare function streamFoundryResponse(_c: unknown, responseId: string, resultStream: AsyncIterable<{
359
- value?: unknown;
360
- }>, options: StreamOptions & {
361
- resultPromise?: Promise<string>;
362
- }): Response;
363
354
  export interface ConversationsClient {
364
355
  saveItems(conversationId: string, items: ReadonlyArray<unknown>): Promise<void>;
365
356
  }
package/dist/index.js CHANGED
@@ -80,6 +80,38 @@ function findLastUserText(items) {
80
80
  }
81
81
  return text;
82
82
  }
83
+ function isContentPart(value) {
84
+ return typeof value === "object" && value !== null && value.type === "input_text" && typeof value.text === "string";
85
+ }
86
+ function isValidContent(content) {
87
+ if (typeof content === "string") {
88
+ return true;
89
+ }
90
+ return Array.isArray(content) && content.every(isContentPart);
91
+ }
92
+ function isInputItem(item) {
93
+ if (typeof item !== "object" || item === null) {
94
+ return false;
95
+ }
96
+ const type = item.type;
97
+ if (type === "message") {
98
+ const role = item.role;
99
+ const content = item.content;
100
+ return typeof role === "string" && isValidContent(content);
101
+ }
102
+ if (type === "function_call_output") {
103
+ const callId = item.call_id;
104
+ const output = item.output;
105
+ return typeof callId === "string" && typeof output === "string";
106
+ }
107
+ if (type === "function_call") {
108
+ const callId = item.call_id;
109
+ const name = item.name;
110
+ const args = item.arguments;
111
+ return typeof callId === "string" && typeof name === "string" && typeof args === "string";
112
+ }
113
+ return false;
114
+ }
83
115
  function extractFunctionCallOutputs(items) {
84
116
  const callMap = /* @__PURE__ */ new Map();
85
117
  for (const item of items) {
@@ -120,6 +152,11 @@ function extractInput(input) {
120
152
  if (!Array.isArray(input) || input.length === 0) {
121
153
  throw new InputError("Missing required field: input");
122
154
  }
155
+ for (const item of input) {
156
+ if (!isInputItem(item)) {
157
+ throw new InputError("Invalid input item shape");
158
+ }
159
+ }
123
160
  const items = input;
124
161
  const hasFunctionCallOutput = items.some(
125
162
  (item) => item.type === "function_call_output"
@@ -136,25 +173,47 @@ function extractInput(input) {
136
173
 
137
174
  // src/session.ts
138
175
  var DEFAULT_MAX_SESSIONS = 10;
139
- function createSessionManager() {
176
+ function createSessionManager(options) {
140
177
  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();
178
+ const parsedEnv = raw !== void 0 ? parseInt(raw, 10) : NaN;
179
+ const envMax = Number.isFinite(parsedEnv) && parsedEnv > 0 ? parsedEnv : DEFAULT_MAX_SESSIONS;
180
+ const max = options?.maxConcurrentSessions ?? envMax;
181
+ const activeTokens = /* @__PURE__ */ new Map();
182
+ const tokensBySession = /* @__PURE__ */ new Map();
144
183
  return {
145
184
  acquire(conversationId) {
146
- if (active.size >= max) {
185
+ if (activeTokens.size >= max) {
147
186
  throw new CapacityError(max);
148
187
  }
149
188
  const sessionId = conversationId ?? generateId("sess_");
150
- active.add(sessionId);
189
+ const token = generateId("tok_");
190
+ activeTokens.set(token, sessionId);
191
+ const tokens = tokensBySession.get(sessionId);
192
+ if (tokens === void 0) {
193
+ tokensBySession.set(sessionId, /* @__PURE__ */ new Set([token]));
194
+ } else {
195
+ tokens.add(token);
196
+ }
151
197
  return sessionId;
152
198
  },
153
199
  release(sessionId) {
154
- active.delete(sessionId);
200
+ const tokens = tokensBySession.get(sessionId);
201
+ if (tokens === void 0) {
202
+ return;
203
+ }
204
+ const iterResult = tokens.values().next();
205
+ if (iterResult.done) {
206
+ return;
207
+ }
208
+ const token = iterResult.value;
209
+ tokens.delete(token);
210
+ if (tokens.size === 0) {
211
+ tokensBySession.delete(sessionId);
212
+ }
213
+ activeTokens.delete(token);
155
214
  },
156
215
  activeCount() {
157
- return active.size;
216
+ return activeTokens.size;
158
217
  }
159
218
  };
160
219
  }
@@ -172,10 +231,12 @@ function coerceResult(result) {
172
231
  }
173
232
  return JSON.stringify(result);
174
233
  }
175
- function buildSyncResponse(result, responseId) {
234
+ function buildSyncResponse(result, responseId, debugErrors = false) {
176
235
  const status = result.state === "completed" ? "completed" : "failed";
177
- const text = coerceResult(result.result);
236
+ const rawText = coerceResult(result.result);
178
237
  const msgId = generateId("msg_");
238
+ const errorMessage = debugErrors ? rawText : GENERIC_MESSAGES["SERVER_ERROR"] ?? FALLBACK_MESSAGE;
239
+ const text = status === "failed" && !debugErrors ? errorMessage : rawText;
179
240
  return {
180
241
  id: responseId,
181
242
  object: "response",
@@ -196,7 +257,7 @@ function buildSyncResponse(result, responseId) {
196
257
  ]
197
258
  }
198
259
  ],
199
- error: status === "failed" ? { code: "SERVER_ERROR", message: text } : null,
260
+ error: status === "failed" ? { code: "SERVER_ERROR", message: errorMessage } : null,
200
261
  metadata: {},
201
262
  temperature: 0,
202
263
  top_p: 0,
@@ -262,170 +323,6 @@ function generateToolDefinitions(router) {
262
323
  ];
263
324
  }
264
325
 
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
326
  // src/conversations.ts
430
327
  var PersistenceError = class extends Error {
431
328
  statusCode;
@@ -523,7 +420,7 @@ async function shutdownTelemetry() {
523
420
  import "hono";
524
421
  import { SpanStatusCode } from "@opentelemetry/api";
525
422
  import { DefaultAzureCredential } from "@azure/identity";
526
- import { validateParams, routerErrorToStatus } from "@rcrsr/rill-agent";
423
+ import { validateParams, routerErrorToStatus as routerErrorToStatus2 } from "@rcrsr/rill-agent";
527
424
 
528
425
  // ../../shared/hono-kit/src/index.ts
529
426
  import { existsSync } from "fs";
@@ -543,18 +440,33 @@ function createHarnessLifecycle(options) {
543
440
  if (server !== void 0) {
544
441
  throw new Error("Server is already listening");
545
442
  }
546
- return new Promise((resolve) => {
547
- server = serve({ fetch: app.fetch, port }, () => {
548
- options?.serverTweaks?.(server);
443
+ return new Promise((resolve, reject) => {
444
+ const started = serve({ fetch: app.fetch, port }, () => {
445
+ started.off("error", onError);
446
+ options?.serverTweaks?.(started);
549
447
  resolve();
550
448
  });
449
+ function onError(err) {
450
+ server = void 0;
451
+ reject(err);
452
+ }
453
+ started.once("error", onError);
454
+ server = started;
551
455
  });
552
456
  }
553
457
  async function close() {
554
- if (server !== void 0) {
555
- server.close();
556
- server = void 0;
458
+ if (server === void 0) return;
459
+ const current = server;
460
+ server = void 0;
461
+ if ("closeAllConnections" in current) {
462
+ current.closeAllConnections();
557
463
  }
464
+ await new Promise((resolve, reject) => {
465
+ current.close((err) => {
466
+ if (err) reject(err);
467
+ else resolve();
468
+ });
469
+ });
558
470
  }
559
471
  return { app, listen, close };
560
472
  }
@@ -587,6 +499,159 @@ async function runRillServe(ctx, start) {
587
499
  });
588
500
  }
589
501
 
502
+ // src/stream.ts
503
+ import { routerErrorToStatus } from "@rcrsr/rill-agent";
504
+ var REDACTED_ERROR_MESSAGE = "Internal server error";
505
+ var encoder = new TextEncoder();
506
+ function sseChunk(event, data) {
507
+ return `event: ${event}
508
+ data: ${data}
509
+
510
+ `;
511
+ }
512
+ function sseHeaders(options) {
513
+ const headers = {
514
+ "content-type": "text/event-stream; charset=utf-8",
515
+ "cache-control": "no-store",
516
+ "x-accel-buffering": "no",
517
+ "x-aml-foundry-agents-metadata": options?.metadataHeader ?? JSON.stringify({
518
+ package: { name: "azure-ai-agentserver-core", version: "1.0.0b17" },
519
+ runtime: {
520
+ python_version: "3.11.0",
521
+ platform: "Linux",
522
+ host_name: "",
523
+ replica_name: ""
524
+ }
525
+ })
526
+ };
527
+ if (options?.sessionId !== void 0) {
528
+ headers["x-agent-session-id"] = options.sessionId;
529
+ }
530
+ if (options?.invocationId !== void 0) {
531
+ headers["x-agent-invocation-id"] = options.invocationId;
532
+ }
533
+ return headers;
534
+ }
535
+ function createFoundryStreamResponse(responseId, options) {
536
+ let seq = 0;
537
+ let closed = false;
538
+ let keepAliveTimer;
539
+ const clearKeepAlive = () => {
540
+ if (keepAliveTimer !== void 0) {
541
+ clearInterval(keepAliveTimer);
542
+ keepAliveTimer = void 0;
543
+ }
544
+ };
545
+ function ev(event, payload) {
546
+ payload["sequence_number"] = seq++;
547
+ return encoder.encode(sseChunk(event, JSON.stringify(payload)));
548
+ }
549
+ function emitDeltas(controller, fullText) {
550
+ const tokens = fullText.split(" ");
551
+ for (let i = 0; i < tokens.length; i++) {
552
+ const piece = i === tokens.length - 1 ? tokens[i] : tokens[i] + " ";
553
+ controller.enqueue(
554
+ ev("response.output_text.delta", {
555
+ type: "response.output_text.delta",
556
+ delta: piece
557
+ })
558
+ );
559
+ }
560
+ }
561
+ function emitCompletion(controller, fullText) {
562
+ if (closed) return;
563
+ controller.enqueue(
564
+ ev("response.output_text.done", {
565
+ type: "response.output_text.done",
566
+ text: fullText
567
+ })
568
+ );
569
+ controller.enqueue(
570
+ ev("response.completed", {
571
+ type: "response.completed",
572
+ response: {
573
+ object: "response",
574
+ id: responseId,
575
+ status: "completed",
576
+ created_at: Math.floor(Date.now() / 1e3),
577
+ output: []
578
+ }
579
+ })
580
+ );
581
+ closed = true;
582
+ controller.close();
583
+ }
584
+ function emitError(controller, err) {
585
+ clearKeepAlive();
586
+ options.onError?.(err);
587
+ if (closed) return;
588
+ const rawMessage = err instanceof Error ? err.message : String(err);
589
+ const message = options.debugErrors === true ? rawMessage : REDACTED_ERROR_MESSAGE;
590
+ const code = routerErrorToStatus(err) === 404 ? "NOT_FOUND" : "SERVER_ERROR";
591
+ controller.enqueue(
592
+ encoder.encode(
593
+ sseChunk(
594
+ "error",
595
+ JSON.stringify({
596
+ type: "error",
597
+ sequence_number: seq++,
598
+ code,
599
+ message,
600
+ param: ""
601
+ })
602
+ )
603
+ )
604
+ );
605
+ closed = true;
606
+ controller.close();
607
+ }
608
+ const body = new ReadableStream({
609
+ start(controller) {
610
+ keepAliveTimer = setInterval(() => {
611
+ if (!closed) {
612
+ controller.enqueue(encoder.encode(": keep-alive\n\n"));
613
+ }
614
+ }, 15e3);
615
+ if (options.chunks !== void 0) {
616
+ (async () => {
617
+ let fullText = "";
618
+ for await (const chunk of options.chunks) {
619
+ if (closed) break;
620
+ fullText += chunk;
621
+ controller.enqueue(
622
+ ev("response.output_text.delta", {
623
+ type: "response.output_text.delta",
624
+ delta: chunk
625
+ })
626
+ );
627
+ }
628
+ if (closed) return;
629
+ clearKeepAlive();
630
+ emitCompletion(controller, fullText);
631
+ })().catch((err) => emitError(controller, err));
632
+ } else if (options.resultPromise !== void 0) {
633
+ options.resultPromise.then((resultText) => {
634
+ if (closed) return;
635
+ clearKeepAlive();
636
+ emitDeltas(controller, resultText);
637
+ emitCompletion(controller, resultText);
638
+ }).catch((err) => emitError(controller, err));
639
+ } else {
640
+ clearKeepAlive();
641
+ emitCompletion(controller, "");
642
+ }
643
+ },
644
+ cancel() {
645
+ closed = true;
646
+ clearKeepAlive();
647
+ }
648
+ });
649
+ return new Response(body, {
650
+ status: 200,
651
+ headers: sseHeaders(options)
652
+ });
653
+ }
654
+
590
655
  // src/harness.ts
591
656
  var DEFAULT_PORT = 8088;
592
657
  function resolvePort(options) {
@@ -613,7 +678,26 @@ function resolveConversationId(conversation) {
613
678
  if (typeof conversation === "string") {
614
679
  return conversation;
615
680
  }
616
- return conversation.id;
681
+ if (typeof conversation === "object" && conversation !== null && typeof conversation.id === "string") {
682
+ return conversation.id;
683
+ }
684
+ throw new InputError("conversation.id must be a string");
685
+ }
686
+ function resolveMetadataResponseId(metadata) {
687
+ if (metadata === void 0 || metadata === null) {
688
+ return void 0;
689
+ }
690
+ if (typeof metadata !== "object") {
691
+ throw new InputError("metadata must be an object");
692
+ }
693
+ const responseId = metadata["response_id"];
694
+ if (responseId === void 0) {
695
+ return void 0;
696
+ }
697
+ if (typeof responseId !== "string") {
698
+ throw new InputError("metadata.response_id must be a string");
699
+ }
700
+ return responseId;
617
701
  }
618
702
  function createFoundryHarness(router, options) {
619
703
  const port = resolvePort(options);
@@ -625,7 +709,9 @@ function createFoundryHarness(router, options) {
625
709
  agentName: agentName ?? router.defaultAgent(),
626
710
  agentVersion
627
711
  });
628
- const sessions = createSessionManager();
712
+ const sessions = createSessionManager({
713
+ maxConcurrentSessions: options?.maxConcurrentSessions
714
+ });
629
715
  const projectEndpoint = process.env["FOUNDRY_PROJECT_ENDPOINT"];
630
716
  const azureCredential = projectEndpoint !== void 0 ? new DefaultAzureCredential() : void 0;
631
717
  const conversationsClient = projectEndpoint !== void 0 && azureCredential !== void 0 ? createConversationsClient(projectEndpoint, azureCredential) : void 0;
@@ -714,7 +800,19 @@ function createFoundryHarness(router, options) {
714
800
  500
715
801
  );
716
802
  }
717
- const conversationId = resolveConversationId(body.conversation);
803
+ let conversationId;
804
+ let metadataResponseId;
805
+ try {
806
+ conversationId = resolveConversationId(body.conversation);
807
+ metadataResponseId = resolveMetadataResponseId(body.metadata);
808
+ } catch (err) {
809
+ errorCount++;
810
+ const msg = err instanceof InputError ? err.message : String(err);
811
+ return c.json(
812
+ buildErrorResponse("INVALID_REQUEST", msg, debugErrors),
813
+ 400
814
+ );
815
+ }
718
816
  const agentName_ = extracted.targetAgent ?? router.defaultAgent();
719
817
  const validationError = validateParams(
720
818
  extracted.params,
@@ -748,10 +846,7 @@ function createFoundryHarness(router, options) {
748
846
  500
749
847
  );
750
848
  }
751
- const idGen = createIdGenerator(
752
- body.metadata?.["response_id"],
753
- conversationId
754
- );
849
+ const idGen = createIdGenerator(metadataResponseId, conversationId);
755
850
  const responseId = idGen.responseId;
756
851
  const invocationId = c.req.header("x-agent-invocation-id") ?? generateId("inv_");
757
852
  const agentSessionId = c.req.query("session") ?? c.req.header("x-agent-session-id") ?? sessionId;
@@ -843,8 +938,7 @@ function createFoundryHarness(router, options) {
843
938
  }
844
939
  };
845
940
  const onChunk = async (chunk) => {
846
- const text = typeof chunk === "string" ? chunk : JSON.stringify(chunk);
847
- pushChunk(text);
941
+ pushChunk(coerceResult(chunk));
848
942
  };
849
943
  const streamContext = { ...runContext, onChunk };
850
944
  router.run(agentName_, runRequest, streamContext).then((result) => {
@@ -852,10 +946,8 @@ function createFoundryHarness(router, options) {
852
946
  span.end();
853
947
  sessions.release(sessionId);
854
948
  if (!result.streamed) {
855
- if (result.result !== null && result.result !== void 0) {
856
- const text = typeof result.result === "string" ? result.result : JSON.stringify(result.result);
857
- if (text !== "") pushChunk(text);
858
- }
949
+ const text = coerceResult(result.result);
950
+ if (text !== "") pushChunk(text);
859
951
  }
860
952
  endChunks();
861
953
  }).catch((err) => {
@@ -869,7 +961,6 @@ function createFoundryHarness(router, options) {
869
961
  });
870
962
  return createFoundryStreamResponse(responseId, {
871
963
  chunks,
872
- idGenerator: idGen,
873
964
  onError: (_err) => {
874
965
  errorCount++;
875
966
  },
@@ -900,7 +991,7 @@ function createFoundryHarness(router, options) {
900
991
  span.end();
901
992
  errorCount++;
902
993
  const msg = err instanceof Error ? err.message : String(err);
903
- const status = routerErrorToStatus(err);
994
+ const status = routerErrorToStatus2(err);
904
995
  if (status === 404) {
905
996
  return c.json(buildErrorResponse("NOT_FOUND", msg, debugErrors), 404);
906
997
  }
@@ -910,7 +1001,7 @@ function createFoundryHarness(router, options) {
910
1001
  );
911
1002
  }
912
1003
  span.end();
913
- const response = buildSyncResponse(result, responseId);
1004
+ const response = buildSyncResponse(result, responseId, debugErrors);
914
1005
  const shouldStore = body.store === true;
915
1006
  if (shouldStore && conversationId !== void 0 && conversationsClient !== void 0) {
916
1007
  try {
@@ -956,11 +1047,11 @@ function createFoundryHarness(router, options) {
956
1047
  );
957
1048
  }
958
1049
  } catch (err) {
959
- if (!(err instanceof CredentialError)) {
960
- const message = err instanceof Error ? err.message : String(err);
961
- throw new CredentialError(message);
1050
+ if (err instanceof CredentialError) {
1051
+ throw err;
962
1052
  }
963
- process.exit(1);
1053
+ const message = err instanceof Error ? err.message : String(err);
1054
+ throw new CredentialError(message);
964
1055
  }
965
1056
  }
966
1057
  await lifecycle.listen(port);
@@ -1014,6 +1105,5 @@ export {
1014
1105
  generateToolDefinitions,
1015
1106
  getTracer,
1016
1107
  initTelemetry,
1017
- shutdownTelemetry,
1018
- streamFoundryResponse
1108
+ shutdownTelemetry
1019
1109
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@rcrsr/rill-agent-foundry",
3
- "version": "0.20.0",
3
+ "version": "0.21.0",
4
4
  "description": "rill agent foundry — Azure-hosted harness factory",
5
5
  "license": "MIT",
6
6
  "author": "Andre Bremer",
@@ -22,19 +22,19 @@
22
22
  "@opentelemetry/api": "^1.9.0"
23
23
  },
24
24
  "dependencies": {
25
- "@azure/identity": "^4.10.0",
26
- "@hono/node-server": "^2.0.1",
27
- "@opentelemetry/exporter-trace-otlp-http": "^0.216.0",
28
- "@opentelemetry/resources": "^2.7.1",
29
- "@opentelemetry/sdk-node": "^0.216.0",
30
- "hono": "^4.12.16",
31
- "@rcrsr/rill-agent": "~0.20.0"
25
+ "@azure/identity": "^4.13.2",
26
+ "@hono/node-server": "^2.1.1",
27
+ "@opentelemetry/exporter-trace-otlp-http": "^0.222.0",
28
+ "@opentelemetry/resources": "^2.11.0",
29
+ "@opentelemetry/sdk-node": "^0.222.0",
30
+ "@rcrsr/rill-agent": "~0.21.0",
31
+ "hono": "^4.13.7"
32
32
  },
33
33
  "devDependencies": {
34
34
  "@opentelemetry/api": "^1.9.0",
35
+ "@rcrsr/rill-agent-hono-kit": "^0.21.0",
35
36
  "dts-bundle-generator": "^9.5.1",
36
- "tsup": "^8.5.0",
37
- "@rcrsr/rill-agent-hono-kit": "^0.20.0"
37
+ "tsup": "^8.5.1"
38
38
  },
39
39
  "files": [
40
40
  "dist"