@agents24/node 0.3.0 → 0.5.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 (41) hide show
  1. package/README.md +27 -3
  2. package/compatibility.json +6 -6
  3. package/dist/bff.cjs +481 -52
  4. package/dist/bff.cjs.map +1 -1
  5. package/dist/bff.d.cts +24 -4
  6. package/dist/bff.d.ts +24 -4
  7. package/dist/bff.js +481 -52
  8. package/dist/bff.js.map +1 -1
  9. package/dist/{client-Rmk3dh_D.d.cts → client-xWr_OGEe.d.cts} +437 -76
  10. package/dist/{client-Rmk3dh_D.d.ts → client-xWr_OGEe.d.ts} +437 -76
  11. package/dist/index.cjs +302 -57
  12. package/dist/index.cjs.map +1 -1
  13. package/dist/index.d.cts +2 -2
  14. package/dist/index.d.ts +2 -2
  15. package/dist/index.js +302 -57
  16. package/dist/index.js.map +1 -1
  17. package/dist/types/bff-agent-resolution.d.ts +41 -0
  18. package/dist/types/bff-agent-resolution.d.ts.map +1 -0
  19. package/dist/types/bff-errors.d.ts +3 -0
  20. package/dist/types/bff-errors.d.ts.map +1 -0
  21. package/dist/types/bff.d.ts +6 -19
  22. package/dist/types/bff.d.ts.map +1 -1
  23. package/dist/types/client.d.ts +4 -2
  24. package/dist/types/client.d.ts.map +1 -1
  25. package/dist/types/embed.d.ts +8 -2
  26. package/dist/types/embed.d.ts.map +1 -1
  27. package/dist/types/generated/customerProfiles.d.ts +11 -0
  28. package/dist/types/generated/customerProfiles.d.ts.map +1 -0
  29. package/dist/types/generated/manifest.d.ts +1 -1
  30. package/dist/types/generated/manifest.d.ts.map +1 -1
  31. package/dist/types/generated/resourceInstallations.d.ts +25 -0
  32. package/dist/types/generated/resourceInstallations.d.ts.map +1 -0
  33. package/dist/types/generated/resourcePackages.d.ts +7 -3
  34. package/dist/types/generated/resourcePackages.d.ts.map +1 -1
  35. package/dist/types/generated/resourcePolicies.d.ts +3 -3
  36. package/dist/types/generated/resourcePolicies.d.ts.map +1 -1
  37. package/dist/types/types.d.ts +389 -61
  38. package/dist/types/types.d.ts.map +1 -1
  39. package/package.json +2 -2
  40. package/dist/types/generated/resourceBundles.d.ts +0 -12
  41. package/dist/types/generated/resourceBundles.d.ts.map +0 -1
package/dist/bff.cjs CHANGED
@@ -2,7 +2,46 @@
2
2
 
3
3
  var crypto = require('crypto');
4
4
 
5
- // src/bff.ts
5
+ // src/bff-agent-resolution.ts
6
+ function clean(value) {
7
+ const result = typeof value === "string" ? value.trim() : "";
8
+ return result || void 0;
9
+ }
10
+ function required(value, field) {
11
+ const result = clean(value);
12
+ if (!result || /[\r\n\0]/.test(result)) throw new Error(`${field} is required.`);
13
+ return result;
14
+ }
15
+ function prepareAgents24BffAgentResolution(options) {
16
+ if (!options.agents.length || options.agents.length > 50) {
17
+ throw new Error("agents must contain between one and 50 Agents.");
18
+ }
19
+ const configured = options.agents.map((item) => {
20
+ const agentId = clean(item.agentId);
21
+ const agentAlias = clean(item.agentAlias);
22
+ if (Boolean(agentId) === Boolean(agentAlias)) {
23
+ throw new Error("Each Agent requires exactly one of agentId or agentAlias.");
24
+ }
25
+ return { agentId, agentAlias, agentVersionId: clean(item.agentVersionId) };
26
+ });
27
+ return async () => {
28
+ const agents = await Promise.all(configured.map(async (item) => {
29
+ if (item.agentAlias) {
30
+ const resolution = await options.client.embed.resolveAgentAlias(item.agentAlias);
31
+ return {
32
+ agentId: required(resolution.agent_id, "agentId"),
33
+ agentAlias: required(item.agentAlias, "agentAlias"),
34
+ agentVersionId: item.agentVersionId
35
+ };
36
+ }
37
+ return { agentId: required(item.agentId, "agentId"), agentVersionId: item.agentVersionId };
38
+ }));
39
+ if (new Set(agents.map((item) => item.agentId)).size !== agents.length) {
40
+ throw new Error("Configured Agent selectors must resolve to unique Agents.");
41
+ }
42
+ return agents;
43
+ };
44
+ }
6
45
 
7
46
  // src/errors.ts
8
47
  var Agents24SDKError = class extends Error {
@@ -41,29 +80,7 @@ var Agents24SDKError = class extends Error {
41
80
  }
42
81
  };
43
82
 
44
- // src/bff.ts
45
- var JSON_LIMIT_BYTES = 1048576;
46
- function normalized(value, field) {
47
- const result = String(value || "").trim();
48
- if (!result || /[\r\n\0]/.test(result)) throw new Error(`${field} is required.`);
49
- return result;
50
- }
51
- function normalizeBasePath(value) {
52
- const path = String(value || "/api/agents24").trim().replace(/\/+$/, "");
53
- if (!path.startsWith("/") || /[\r\n\0?#]/.test(path)) throw new Error("basePath must be an absolute path.");
54
- return path;
55
- }
56
- function browserRequestIsSameOrigin(request) {
57
- const fetchSite = String(request.headers.get("sec-fetch-site") || "").trim().toLowerCase();
58
- if (fetchSite) return fetchSite === "same-origin";
59
- const rawOrigin = String(request.headers.get("origin") || "").trim();
60
- if (!rawOrigin) return true;
61
- try {
62
- return rawOrigin !== "null" && new URL(rawOrigin).origin === new URL(request.url).origin;
63
- } catch {
64
- return false;
65
- }
66
- }
83
+ // src/bff-errors.ts
67
84
  function publicError(status, code, message, retryable = false, violations) {
68
85
  const category = status === 401 ? "authentication" : status === 403 ? "authorization" : status === 404 ? "not_found" : status === 409 ? "conflict" : status === 429 ? "rate_limit" : status >= 500 ? "internal" : "validation";
69
86
  return Response.json({
@@ -142,13 +159,79 @@ function errorResponse(error) {
142
159
  const sdkError = error;
143
160
  const status = sdkError.status && sdkError.status >= 400 && sdkError.status <= 599 ? sdkError.status : 502;
144
161
  const policyError = safeResourcePolicyError(sdkError.failure);
145
- if (policyError) {
146
- return resourcePolicyFailureResponse(status, policyError);
147
- }
162
+ if (policyError) return resourcePolicyFailureResponse(status, policyError);
148
163
  return publicError(status, `UPSTREAM_${status}`, "The Agent request failed.", status >= 500);
149
164
  }
150
165
  return publicError(500, "BFF_REQUEST_FAILED", "The Agent request could not be completed.", true);
151
166
  }
167
+
168
+ // src/bff.ts
169
+ var JSON_LIMIT_BYTES = 1048576;
170
+ var MAX_INPUT_LENGTH = 2e5;
171
+ var MAX_ATTACHMENTS = 10;
172
+ var MAX_ATTACHMENT_BYTES = 25 * 1024 * 1024;
173
+ var HITL_ACTIONS = /* @__PURE__ */ new Set(["approve", "reject", "connect", "skip", "respond"]);
174
+ var CUSTOMER_IDENTITY_CACHE_TTL_MS = 6e4;
175
+ var CUSTOMER_IDENTITY_CACHE_MAX_ENTRIES = 1024;
176
+ function createCustomerIdentityResolver(embed, integrationId, integrationName) {
177
+ const cache = /* @__PURE__ */ new Map();
178
+ return async (principal) => {
179
+ const profile = {
180
+ display_name: principal.displayName,
181
+ email: principal.email,
182
+ avatar_url: principal.avatarUrl
183
+ };
184
+ const profileFields = Object.fromEntries(Object.entries(profile).filter(([, value2]) => value2 !== void 0));
185
+ const key = JSON.stringify([integrationId, principal.issuer, principal.subject, profileFields]);
186
+ const now = Date.now();
187
+ const cached = cache.get(key);
188
+ if (cached && cached.expiresAt > now) {
189
+ cache.delete(key);
190
+ cache.set(key, cached);
191
+ return cached.value;
192
+ }
193
+ if (cached) cache.delete(key);
194
+ const value = embed.resolveCustomerPrincipal({
195
+ integration_id: integrationId,
196
+ ...integrationName ? { integration_name: integrationName } : {},
197
+ ...principal.issuer ? { issuer: principal.issuer } : {},
198
+ subject: principal.subject,
199
+ ...Object.keys(profileFields).length ? { profile: profileFields } : {}
200
+ }).then((result) => normalized(result.customer_principal_ref, "customer_principal_ref"));
201
+ const entry = { expiresAt: now + CUSTOMER_IDENTITY_CACHE_TTL_MS, value };
202
+ cache.set(key, entry);
203
+ while (cache.size > CUSTOMER_IDENTITY_CACHE_MAX_ENTRIES) {
204
+ const oldest = cache.keys().next().value;
205
+ if (oldest === void 0) break;
206
+ cache.delete(oldest);
207
+ }
208
+ void value.catch(() => {
209
+ if (cache.get(key) === entry) cache.delete(key);
210
+ });
211
+ return value;
212
+ };
213
+ }
214
+ function normalized(value, field) {
215
+ const result = String(value || "").trim();
216
+ if (!result || /[\r\n\0]/.test(result)) throw new Error(`${field} is required.`);
217
+ return result;
218
+ }
219
+ function normalizeBasePath(value) {
220
+ const path = String(value || "/api/agents24").trim().replace(/\/+$/, "");
221
+ if (!path.startsWith("/") || /[\r\n\0?#]/.test(path)) throw new Error("basePath must be an absolute path.");
222
+ return path;
223
+ }
224
+ function browserRequestIsSameOrigin(request) {
225
+ const fetchSite = String(request.headers.get("sec-fetch-site") || "").trim().toLowerCase();
226
+ if (fetchSite) return fetchSite === "same-origin";
227
+ const rawOrigin = String(request.headers.get("origin") || "").trim();
228
+ if (!rawOrigin) return true;
229
+ try {
230
+ return rawOrigin !== "null" && new URL(rawOrigin).origin === new URL(request.url).origin;
231
+ } catch {
232
+ return false;
233
+ }
234
+ }
152
235
  function mergeHeaders(response, additional) {
153
236
  if (!additional) return response;
154
237
  const headers = new Headers(response.headers);
@@ -206,7 +289,7 @@ function sseHeaders(headers) {
206
289
  result.set("X-Accel-Buffering", "no");
207
290
  return result;
208
291
  }
209
- function sseResponse(execute, headers) {
292
+ function sseResponse(execute, headers, failurePayload) {
210
293
  const stream = new TransformStream();
211
294
  const writer = stream.writable.getWriter();
212
295
  const encoder = new TextEncoder();
@@ -214,6 +297,10 @@ function sseResponse(execute, headers) {
214
297
 
215
298
  `));
216
299
  void execute(write).catch(async (error) => {
300
+ if (failurePayload) {
301
+ await write(failurePayload(error)).catch(() => void 0);
302
+ return;
303
+ }
217
304
  const message = "The Agent stream failed.";
218
305
  const event = {
219
306
  version: "run-stream.v3",
@@ -237,7 +324,7 @@ function bootstrapDocument(raw, agentVersionId) {
237
324
  version: String(raw.version || "agents24.embed.bootstrap.v1"),
238
325
  deployment: {
239
326
  id: "server-embed",
240
- name: String(raw.agent_name || "Agents24 Agent"),
327
+ name: normalized(String(raw.agent_name || ""), "agent_name"),
241
328
  version_policy: agentVersionId ? { mode: "pinned", version_id: agentVersionId } : { mode: "latest_published" },
242
329
  resolved_agent_version_id: resolvedVersionId,
243
330
  resolved_agent_version_number: Number.isFinite(versionNumber) ? versionNumber : 0,
@@ -249,9 +336,12 @@ function bootstrapDocument(raw, agentVersionId) {
249
336
  native_profiles: []
250
337
  };
251
338
  }
252
- function createAgents24BffHandler(options) {
339
+ function createAgents24BffHandlerWithResolver(options, resolveCustomerIdentity) {
253
340
  const agentId = normalized(options.agentId, "agentId");
254
341
  const agentVersionId = stringValue(options.agentVersionId);
342
+ const runtimeTarget = options.runtimeTarget || "published";
343
+ const integrationId = normalized(options.integrationId, "integrationId");
344
+ if (runtimeTarget === "draft" && agentVersionId) throw new Error("agentVersionId cannot be used with draft runtimeTarget.");
255
345
  const basePath = normalizeBasePath(options.basePath);
256
346
  const embed = options.client.embed;
257
347
  return async (request) => {
@@ -276,42 +366,44 @@ function createAgents24BffHandler(options) {
276
366
  const principal = resolution.principal;
277
367
  const idempotencyKey = stringValue(request.headers.get("idempotency-key"));
278
368
  try {
279
- let customerPrincipalRef = "";
280
- if (!(request.method === "GET" && (path === "/bootstrap" || path === "/resource-policy"))) {
281
- const entitlement = await embed.getResourcePolicy(agentId, {
282
- external_user_id: principal.subject,
283
- ...principal.issuer ? { external_user_issuer: principal.issuer } : {},
284
- ...agentVersionId ? { agent_version_id: agentVersionId } : {}
285
- });
286
- customerPrincipalRef = stringValue(entitlement.customer_principal_ref) || principal.subject;
287
- }
369
+ const customerPrincipalRef = await resolveCustomerIdentity(principal);
288
370
  const identity = {
289
371
  external_user_id: customerPrincipalRef,
290
372
  ...principal.externalSessionId ? { external_session_id: principal.externalSessionId } : {}
291
373
  };
292
374
  const threadIdentity = {
293
375
  externalUserId: customerPrincipalRef,
376
+ runtimeTarget,
294
377
  ...principal.externalSessionId ? { externalSessionId: principal.externalSessionId } : {}
295
378
  };
296
379
  let response;
297
380
  if (request.method === "GET" && path === "/bootstrap") {
298
- const raw = await embed.getRuntimeBootstrap(agentId, agentVersionId);
381
+ const raw = await embed.getRuntimeBootstrap(agentId, agentVersionId, runtimeTarget);
299
382
  response = json(bootstrapDocument(raw, agentVersionId));
300
383
  } else if (request.method === "GET" && path === "/resource-policy") {
301
384
  response = json(await embed.getResourcePolicy(agentId, {
302
- external_user_id: principal.subject,
303
- ...principal.issuer ? { external_user_issuer: principal.issuer } : {},
304
- ...agentVersionId ? { agent_version_id: agentVersionId } : {}
385
+ external_user_id: customerPrincipalRef,
386
+ integration_id: integrationId,
387
+ ...agentVersionId ? { agent_version_id: agentVersionId } : {},
388
+ runtime_target: runtimeTarget
305
389
  }));
306
390
  } else if (request.method === "POST" && path === "/chat/stream") {
307
391
  const body = await requestJson(request);
308
392
  const input = stringValue(body.input);
309
393
  const attachmentIds = stringList(body.attachmentIds);
310
394
  if (!input && attachmentIds.length === 0) throw new BffInputError(400, "INPUT_REQUIRED", "A message or attachment is required.");
395
+ if (input && input.length > MAX_INPUT_LENGTH) {
396
+ throw new BffInputError(413, "INPUT_TOO_LARGE", "The message is too large.");
397
+ }
398
+ if (attachmentIds.length > MAX_ATTACHMENTS) {
399
+ throw new BffInputError(400, "TOO_MANY_ATTACHMENTS", `At most ${MAX_ATTACHMENTS} attachments are allowed.`);
400
+ }
311
401
  response = sseResponse(async (write) => {
312
402
  await embed.streamAgent(agentId, {
313
403
  ...identity,
404
+ integration_id: integrationId,
314
405
  ...agentVersionId ? { agent_version_id: agentVersionId } : {},
406
+ runtime_target: runtimeTarget,
315
407
  ...input ? { input } : {},
316
408
  attachment_ids: attachmentIds,
317
409
  ...stringValue(body.threadId) ? { thread_id: stringValue(body.threadId) } : {},
@@ -331,6 +423,8 @@ function createAgents24BffHandler(options) {
331
423
  response = sseResponse(async (write) => {
332
424
  await embed.attachAgentRun(agentId, runId, {
333
425
  ...identity,
426
+ runtime_target: runtimeTarget,
427
+ integration_id: integrationId,
334
428
  ...cursor === void 0 ? {} : { cursor: Number(cursor) }
335
429
  }, (event) => write(event), {
336
430
  idempotencyKey,
@@ -339,7 +433,10 @@ function createAgents24BffHandler(options) {
339
433
  });
340
434
  } else if (request.method === "POST" && /^\/runs\/[^/]+\/cancel$/.test(path)) {
341
435
  const runId = routeId(path, /^\/runs\/([^/]+)\/cancel$/, "runId");
342
- response = json(await embed.cancelAgentRun(agentId, runId, threadIdentity, { idempotencyKey }));
436
+ response = json(await embed.cancelAgentRun(agentId, runId, {
437
+ ...threadIdentity,
438
+ integrationId
439
+ }, { idempotencyKey }));
343
440
  } else if (request.method === "PUT" && /^\/runs\/[^/]+\/feedback$/.test(path)) {
344
441
  const runId = routeId(path, /^\/runs\/([^/]+)\/feedback$/, "runId");
345
442
  const body = await requestJson(request);
@@ -358,6 +455,8 @@ function createAgents24BffHandler(options) {
358
455
  }
359
456
  response = json(await embed.setAgentRunFeedback(agentId, runId, {
360
457
  ...identity,
458
+ runtime_target: runtimeTarget,
459
+ integration_id: integrationId,
361
460
  rating,
362
461
  ...rating === "dislike" && reason ? { reason } : {},
363
462
  ...rating === "dislike" && comment ? { comment } : {}
@@ -365,43 +464,71 @@ function createAgents24BffHandler(options) {
365
464
  } else if (request.method === "GET" && path === "/threads") {
366
465
  const skip = Number(url.searchParams.get("skip") || 0);
367
466
  const limit = Number(url.searchParams.get("limit") || 20);
467
+ if (!Number.isInteger(skip) || skip < 0 || !Number.isInteger(limit) || limit < 1 || limit > 100) {
468
+ throw new BffInputError(400, "INVALID_PAGINATION", "skip must be non-negative and limit must be between 1 and 100.");
469
+ }
368
470
  response = json(await embed.listAgentThreads(agentId, {
369
471
  ...threadIdentity,
370
- skip: Number.isFinite(skip) ? skip : 0,
371
- limit: Number.isFinite(limit) ? limit : 20
472
+ integrationId,
473
+ runtimeTarget,
474
+ skip,
475
+ limit
372
476
  }));
373
477
  } else if (request.method === "GET" && path === "/threads/events") {
374
478
  const rawCursor = url.searchParams.get("cursor");
375
479
  const cursor = rawCursor === null ? void 0 : Number(rawCursor);
480
+ if (cursor !== void 0 && (!Number.isInteger(cursor) || cursor < 0)) {
481
+ throw new BffInputError(400, "INVALID_CURSOR", "cursor must be a non-negative integer.");
482
+ }
376
483
  response = sseResponse(async (write) => {
377
484
  await embed.subscribeAgentThreadEvents(agentId, {
378
485
  ...threadIdentity,
379
- ...cursor !== void 0 && Number.isFinite(cursor) ? { cursor } : {},
486
+ integrationId,
487
+ ...cursor !== void 0 ? { cursor } : {},
380
488
  signal: request.signal
381
489
  }, (event) => write(event));
382
- });
490
+ }, void 0, () => ({
491
+ schema_version: "agents24.thread_summary_event.v1",
492
+ event: "snapshot_required",
493
+ cursor,
494
+ reason: "stream_failed"
495
+ }));
383
496
  } else if (/^\/threads\/[^/]+$/.test(path) && request.method === "GET") {
384
497
  const threadId = routeId(path, /^\/threads\/([^/]+)$/, "threadId");
385
498
  const rawBefore = url.searchParams.get("before_turn_index");
386
499
  const rawLimit = url.searchParams.get("limit");
387
500
  const before = rawBefore === null ? void 0 : Number(rawBefore);
388
501
  const limit = rawLimit === null ? void 0 : Number(rawLimit);
502
+ if (before !== void 0 && (!Number.isInteger(before) || before < 0)) {
503
+ throw new BffInputError(400, "INVALID_PAGINATION", "before_turn_index is invalid.");
504
+ }
505
+ if (limit !== void 0 && (!Number.isInteger(limit) || limit < 1 || limit > 100)) {
506
+ throw new BffInputError(400, "INVALID_PAGINATION", "limit must be between 1 and 100.");
507
+ }
389
508
  response = json(await embed.getAgentThread(agentId, threadId, {
390
509
  ...threadIdentity,
391
- ...before !== void 0 && Number.isFinite(before) ? { beforeTurnIndex: before } : {},
392
- ...limit !== void 0 && Number.isFinite(limit) ? { limit } : {}
510
+ integrationId,
511
+ ...before !== void 0 ? { beforeTurnIndex: before } : {},
512
+ ...limit !== void 0 ? { limit } : {}
393
513
  }));
394
514
  } else if (/^\/threads\/[^/]+$/.test(path) && request.method === "DELETE") {
395
515
  const threadId = routeId(path, /^\/threads\/([^/]+)$/, "threadId");
396
- response = json(await embed.deleteAgentThread(agentId, threadId, threadIdentity, { idempotencyKey }));
516
+ response = json(await embed.deleteAgentThread(agentId, threadId, { ...threadIdentity, integrationId }, { idempotencyKey }));
397
517
  } else if (request.method === "POST" && path === "/attachments/upload") {
398
518
  const form = await request.formData();
399
519
  const files = form.getAll("files").filter((entry) => entry instanceof File);
400
520
  if (files.length === 0) throw new BffInputError(400, "ATTACHMENT_REQUIRED", "At least one attachment is required.");
521
+ if (files.length > MAX_ATTACHMENTS) {
522
+ throw new BffInputError(400, "TOO_MANY_ATTACHMENTS", `At most ${MAX_ATTACHMENTS} attachments are allowed.`);
523
+ }
524
+ if (files.some((file) => file.size > MAX_ATTACHMENT_BYTES)) {
525
+ throw new BffInputError(413, "ATTACHMENT_TOO_LARGE", "An attachment exceeds the allowed size.");
526
+ }
401
527
  response = json(await embed.uploadAgentAttachments(agentId, {
402
528
  ...threadIdentity,
403
529
  files,
404
530
  ...stringValue(form.get("thread_id")) ? { threadId: stringValue(form.get("thread_id")) } : {},
531
+ integrationId,
405
532
  idempotencyKey
406
533
  }));
407
534
  } else if (request.method === "POST" && /^\/attachments\/[^/]+\/content-access$/.test(path)) {
@@ -414,6 +541,8 @@ function createAgents24BffHandler(options) {
414
541
  response = json(await embed.createAttachmentContentAccess(agentId, attachmentId, {
415
542
  external_user_id: threadIdentity.externalUserId,
416
543
  ...threadIdentity.externalSessionId ? { external_session_id: threadIdentity.externalSessionId } : {},
544
+ runtime_target: runtimeTarget,
545
+ integration_id: integrationId,
417
546
  disposition
418
547
  }));
419
548
  } else if (request.method === "POST" && /^\/runs\/[^/]+\/hitl\/[^/]+\/resume$/.test(path)) {
@@ -421,11 +550,17 @@ function createAgents24BffHandler(options) {
421
550
  const runId = routeId(path, /^\/runs\/([^/]+)\/hitl\//, "runId");
422
551
  const interruptId = match?.[2] ? decodeURIComponent(match[2]) : "";
423
552
  const body = await requestJson(request);
553
+ const action = stringValue(body.action);
554
+ if (!action || !HITL_ACTIONS.has(action)) {
555
+ throw new BffInputError(400, "INVALID_HITL_ACTION", "The HITL action is invalid.");
556
+ }
424
557
  response = json(await embed.resumeAgentRun(agentId, runId, {
425
558
  ...identity,
559
+ runtime_target: runtimeTarget,
560
+ integration_id: integrationId,
426
561
  schema_version: "agents24.hitl.resume.v2",
427
562
  interrupt_id: normalized(interruptId, "interruptId"),
428
- action: normalized(String(body.action || ""), "action"),
563
+ action,
429
564
  ...stringValue(body.comment) ? { comment: stringValue(body.comment) } : {},
430
565
  ...Array.isArray(body.answers) ? { answers: body.answers } : {}
431
566
  }, { idempotencyKey }));
@@ -437,6 +572,8 @@ function createAgents24BffHandler(options) {
437
572
  const callbackOrigin = new URL(request.url).origin;
438
573
  response = json(await embed.startAgentRunMcpAuth(agentId, normalized(runId, "runId"), normalized(serverId, "serverId"), {
439
574
  ...identity,
575
+ runtime_target: runtimeTarget,
576
+ integration_id: integrationId,
440
577
  interrupt_id: normalized(String(body.interruptId || ""), "interruptId"),
441
578
  callback_origin: callbackOrigin,
442
579
  popup_nonce: normalized(String(body.popupNonce || ""), "popupNonce")
@@ -458,6 +595,298 @@ function createAgents24BffHandler(options) {
458
595
  }
459
596
  };
460
597
  }
598
+ function createResolvedAgents24BffHandler(options) {
599
+ const runtimeTarget = options.runtimeTarget || "published";
600
+ const basePath = normalizeBasePath(options.basePath);
601
+ const integrationId = normalized(options.integrationId, "integrationId");
602
+ const agents = options.agents.map((item) => ({
603
+ agentId: normalized(item.agentId, "agentId"),
604
+ agentAlias: stringValue(item.agentAlias),
605
+ agentVersionId: stringValue(item.agentVersionId)
606
+ }));
607
+ if (!agents.length || agents.length > 50) throw new Error("agents must contain between one and 50 Agents.");
608
+ if (new Set(agents.map((agent) => agent.agentId)).size !== agents.length) throw new Error("agents must contain unique Agent IDs.");
609
+ if (runtimeTarget === "draft" && agents.some((agent) => agent.agentVersionId)) {
610
+ throw new Error("agentVersionId cannot be used with draft runtimeTarget.");
611
+ }
612
+ const resolveCustomerIdentity = createCustomerIdentityResolver(
613
+ options.client.embed,
614
+ integrationId,
615
+ stringValue(options.integrationName)
616
+ );
617
+ const scopedHandlers = new Map(agents.map((agent) => [
618
+ agent.agentId,
619
+ createAgents24BffHandlerWithResolver({
620
+ client: options.client,
621
+ agentId: agent.agentId,
622
+ agentVersionId: agent.agentVersionId,
623
+ runtimeTarget,
624
+ integrationId,
625
+ integrationName: options.integrationName,
626
+ basePath: `${basePath}/agents/${encodeURIComponent(agent.agentId)}`,
627
+ resolvePrincipal: options.resolvePrincipal,
628
+ revokePrincipal: options.revokePrincipal
629
+ }, resolveCustomerIdentity)
630
+ ]));
631
+ const agentBySelector = /* @__PURE__ */ new Map();
632
+ for (const agent of agents) {
633
+ agentBySelector.set(agent.agentId, agent);
634
+ if (agent.agentAlias) agentBySelector.set(agent.agentAlias, agent);
635
+ }
636
+ return async (request) => {
637
+ const url = new URL(request.url);
638
+ if (url.pathname !== basePath && !url.pathname.startsWith(`${basePath}/`)) {
639
+ return publicError(404, "NOT_FOUND", "The requested BFF route does not exist.");
640
+ }
641
+ if (!browserRequestIsSameOrigin(request)) {
642
+ return publicError(403, "BFF_ORIGIN_DENIED", "The BFF accepts same-origin browser requests only.");
643
+ }
644
+ const path = url.pathname.slice(basePath.length) || "/";
645
+ let resolution;
646
+ try {
647
+ resolution = await options.resolvePrincipal(request);
648
+ resolution.principal.subject = normalized(resolution.principal.subject, "subject");
649
+ if (resolution.principal.issuer !== void 0) resolution.principal.issuer = normalized(resolution.principal.issuer, "issuer");
650
+ } catch {
651
+ return publicError(401, "PRINCIPAL_REQUIRED", "A valid application session is required.");
652
+ }
653
+ try {
654
+ const externalUserId = await resolveCustomerIdentity(resolution.principal);
655
+ const identity = {
656
+ external_user_id: externalUserId,
657
+ ...resolution.principal.externalSessionId ? { external_session_id: resolution.principal.externalSessionId } : {}
658
+ };
659
+ const requestedAgent = (value) => {
660
+ const agentId = stringValue(value) || agents[0].agentId;
661
+ const configured = agentBySelector.get(agentId);
662
+ if (!configured) throw new BffInputError(404, "AGENT_NOT_CONFIGURED", "The requested Agent is not available.");
663
+ return configured;
664
+ };
665
+ const dispatchToAgent = async (agentId) => {
666
+ const handler = scopedHandlers.get(agentId);
667
+ if (!handler) throw new BffInputError(404, "NOT_FOUND", "The requested resource does not exist.");
668
+ const target = new URL(request.url);
669
+ target.pathname = `${basePath}/agents/${encodeURIComponent(agentId)}${path}`;
670
+ const body = request.method === "GET" || request.method === "HEAD" ? void 0 : await request.arrayBuffer();
671
+ return handler(new Request(target, {
672
+ method: request.method,
673
+ headers: request.headers,
674
+ body,
675
+ signal: request.signal
676
+ }));
677
+ };
678
+ const resolveRunAgent = async (runId) => {
679
+ const result = await options.client.embed.resolveConversationRun(runId, {
680
+ agent_ids: agents.map((agent) => agent.agentId),
681
+ ...identity,
682
+ integration_id: integrationId,
683
+ runtime_target: runtimeTarget
684
+ });
685
+ return requestedAgent(result.agent_id).agentId;
686
+ };
687
+ if (request.method === "GET" && path === "/bootstrap") {
688
+ const candidates = await Promise.all(agents.map(async (agent) => {
689
+ const [raw, policy] = await Promise.all([
690
+ options.client.embed.getRuntimeBootstrap(agent.agentId, agent.agentVersionId, runtimeTarget),
691
+ options.client.embed.getResourcePolicy(agent.agentId, {
692
+ external_user_id: externalUserId,
693
+ integration_id: integrationId,
694
+ ...agent.agentVersionId ? { agent_version_id: agent.agentVersionId } : {},
695
+ runtime_target: runtimeTarget
696
+ })
697
+ ]);
698
+ const alias = normalized(String(raw.agent_alias || ""), "agent_alias");
699
+ agent.agentAlias = alias;
700
+ agentBySelector.set(alias, agent);
701
+ for (const item of stringList(raw.agent_aliases)) agentBySelector.set(item, agent);
702
+ return { agent, raw, policy };
703
+ }));
704
+ const selected = requestedAgent(url.searchParams.get("agent_id"));
705
+ const allowed = candidates.filter((candidate) => candidate.policy.agent_access_allowed !== false);
706
+ const selectedCandidate = allowed.find((candidate) => candidate.agent.agentId === selected.agentId) || allowed[0];
707
+ if (!selectedCandidate) throw new BffInputError(403, "RESOURCE_POLICY_ACCESS_DENIED", "No configured Agent is available.");
708
+ const document = bootstrapDocument(selectedCandidate.raw, selectedCandidate.agent.agentVersionId);
709
+ const features = objectValue(document.features);
710
+ features.models = selectedCandidate.policy.model_selection;
711
+ features.agents = {
712
+ selectable: allowed.length > 1,
713
+ default_id: normalized(String(allowed[0].raw.agent_alias || ""), "agent_alias"),
714
+ options: allowed.map((candidate) => ({
715
+ id: normalized(String(candidate.raw.agent_alias || ""), "agent_alias"),
716
+ label: normalized(String(candidate.raw.agent_name || ""), "agent_name")
717
+ }))
718
+ };
719
+ document.features = features;
720
+ document.capabilities = [
721
+ .../* @__PURE__ */ new Set([
722
+ ...document.capabilities || [],
723
+ ...allowed.length > 1 ? ["agents.select"] : [],
724
+ ...selectedCandidate.policy.model_selection.selectable ? ["models.select"] : []
725
+ ])
726
+ ];
727
+ return mergeHeaders(json(document), resolution.responseHeaders);
728
+ }
729
+ if (request.method === "POST" && path === "/chat/stream") {
730
+ const body = await requestJson(request);
731
+ const selected = requestedAgent(body.requestedAgentId);
732
+ const input = stringValue(body.input);
733
+ const attachmentIds = stringList(body.attachmentIds);
734
+ if (!input && attachmentIds.length === 0) throw new BffInputError(400, "INPUT_REQUIRED", "A message or attachment is required.");
735
+ if (input && input.length > MAX_INPUT_LENGTH) throw new BffInputError(413, "INPUT_TOO_LARGE", "The message is too large.");
736
+ if (attachmentIds.length > MAX_ATTACHMENTS) throw new BffInputError(400, "TOO_MANY_ATTACHMENTS", `At most ${MAX_ATTACHMENTS} attachments are allowed.`);
737
+ return mergeHeaders(sseResponse(async (write) => {
738
+ await options.client.embed.streamAgent(selected.agentId, {
739
+ ...identity,
740
+ ...selected.agentVersionId ? { agent_version_id: selected.agentVersionId } : {},
741
+ runtime_target: runtimeTarget,
742
+ integration_id: integrationId,
743
+ ...input ? { input } : {},
744
+ attachment_ids: attachmentIds,
745
+ ...stringValue(body.threadId) ? { thread_id: stringValue(body.threadId) } : {},
746
+ ...stringValue(body.requestedModelId) ? { requested_model_id: stringValue(body.requestedModelId) } : {},
747
+ tool_inputs: objectValue(body.toolInputs),
748
+ metadata: objectValue(body.metadata),
749
+ client: objectValue(body.client)
750
+ }, (event) => write(event), { idempotencyKey: stringValue(request.headers.get("idempotency-key")), signal: request.signal });
751
+ }), resolution.responseHeaders);
752
+ }
753
+ if (request.method === "GET" && path === "/threads") {
754
+ const skip = Number(url.searchParams.get("skip") || 0);
755
+ const limit = Number(url.searchParams.get("limit") || 20);
756
+ if (!Number.isInteger(skip) || skip < 0 || !Number.isInteger(limit) || limit < 1 || limit > 100) {
757
+ throw new BffInputError(400, "INVALID_PAGINATION", "skip must be non-negative and limit must be between 1 and 100.");
758
+ }
759
+ return mergeHeaders(json(await options.client.embed.listAgentThreadsMulti({
760
+ agent_ids: agents.map((agent) => agent.agentId),
761
+ external_user_id: externalUserId,
762
+ ...resolution.principal.externalSessionId ? { external_session_id: resolution.principal.externalSessionId } : {},
763
+ runtime_target: runtimeTarget,
764
+ integration_id: integrationId,
765
+ skip,
766
+ limit
767
+ })), resolution.responseHeaders);
768
+ }
769
+ if (request.method === "GET" && path === "/threads/events") {
770
+ const rawCursor = url.searchParams.get("cursor");
771
+ const cursor = rawCursor === null ? void 0 : Number(rawCursor);
772
+ if (cursor !== void 0 && (!Number.isInteger(cursor) || cursor < 0)) {
773
+ throw new BffInputError(400, "INVALID_CURSOR", "cursor must be a non-negative integer.");
774
+ }
775
+ return mergeHeaders(sseResponse(async (write) => {
776
+ await options.client.embed.subscribeAgentThreadEventsMulti({
777
+ agentIds: agents.map((agent) => agent.agentId),
778
+ externalUserId,
779
+ ...resolution.principal.externalSessionId ? { externalSessionId: resolution.principal.externalSessionId } : {},
780
+ ...cursor === void 0 ? {} : { cursor },
781
+ runtimeTarget,
782
+ integrationId,
783
+ signal: request.signal
784
+ }, (event) => write(event));
785
+ }, void 0, () => ({
786
+ schema_version: "agents24.thread_summary_event.v1",
787
+ event: "snapshot_required",
788
+ cursor,
789
+ reason: "stream_failed"
790
+ })), resolution.responseHeaders);
791
+ }
792
+ if (/^\/threads\/[^/]+$/.test(path) && request.method === "GET") {
793
+ const threadId = routeId(path, /^\/threads\/([^/]+)$/, "threadId");
794
+ const rawBefore = url.searchParams.get("before_turn_index");
795
+ const rawLimit = url.searchParams.get("limit");
796
+ const beforeTurnIndex = rawBefore === null ? void 0 : Number(rawBefore);
797
+ const detailLimit = rawLimit === null ? void 0 : Number(rawLimit);
798
+ if (beforeTurnIndex !== void 0 && (!Number.isInteger(beforeTurnIndex) || beforeTurnIndex < 0)) {
799
+ throw new BffInputError(400, "INVALID_PAGINATION", "before_turn_index is invalid.");
800
+ }
801
+ if (detailLimit !== void 0 && (!Number.isInteger(detailLimit) || detailLimit < 1 || detailLimit > 100)) {
802
+ throw new BffInputError(400, "INVALID_PAGINATION", "limit must be between 1 and 100.");
803
+ }
804
+ return mergeHeaders(json(await options.client.embed.getConversationThread(threadId, {
805
+ agentIds: agents.map((agent) => agent.agentId),
806
+ externalUserId,
807
+ ...resolution.principal.externalSessionId ? { externalSessionId: resolution.principal.externalSessionId } : {},
808
+ integrationId,
809
+ runtimeTarget,
810
+ beforeTurnIndex,
811
+ limit: detailLimit
812
+ })), resolution.responseHeaders);
813
+ }
814
+ if (/^\/threads\/[^/]+\/runtime-selection$/.test(path) && request.method === "PATCH") {
815
+ const threadId = routeId(path, /^\/threads\/([^/]+)\//, "threadId");
816
+ const body = await requestJson(request);
817
+ const selected = requestedAgent(body.preferredAgentId);
818
+ return mergeHeaders(json(await options.client.embed.setConversationRuntimeSelection(threadId, {
819
+ agent_ids: agents.map((agent) => agent.agentId),
820
+ preferred_agent_id: selected.agentId,
821
+ ...stringValue(body.preferredModelId) ? { preferred_model_id: stringValue(body.preferredModelId) } : {},
822
+ ...identity,
823
+ integration_id: integrationId,
824
+ runtime_target: runtimeTarget
825
+ })), resolution.responseHeaders);
826
+ }
827
+ if (/^\/threads\/[^/]+$/.test(path) && request.method === "DELETE") {
828
+ const threadId = routeId(path, /^\/threads\/([^/]+)$/, "threadId");
829
+ return mergeHeaders(json(await options.client.embed.deleteConversationThread(threadId, {
830
+ agentIds: agents.map((agent) => agent.agentId),
831
+ externalUserId,
832
+ ...resolution.principal.externalSessionId ? { externalSessionId: resolution.principal.externalSessionId } : {},
833
+ integrationId,
834
+ runtimeTarget
835
+ }, { idempotencyKey: stringValue(request.headers.get("idempotency-key")) })), resolution.responseHeaders);
836
+ }
837
+ if (request.method === "POST" && path === "/attachments/upload") {
838
+ const form = await request.clone().formData();
839
+ const selected = requestedAgent(form.get("requested_agent_id"));
840
+ const policy = await options.client.embed.getResourcePolicy(selected.agentId, {
841
+ external_user_id: externalUserId,
842
+ integration_id: integrationId,
843
+ ...selected.agentVersionId ? { agent_version_id: selected.agentVersionId } : {},
844
+ runtime_target: runtimeTarget
845
+ });
846
+ if (policy.agent_access_allowed === false) throw new BffInputError(403, "RESOURCE_POLICY_ACCESS_DENIED", "The requested Agent is not available.");
847
+ return dispatchToAgent(selected.agentId);
848
+ }
849
+ if (request.method === "POST" && /^\/attachments\/[^/]+\/content-access$/.test(path)) {
850
+ return dispatchToAgent(agents[0].agentId);
851
+ }
852
+ if (/^\/runs\/[^/]+\/(?:attach|cancel|feedback)$/.test(path) || /^\/runs\/[^/]+\/hitl\/[^/]+\/resume$/.test(path) || /^\/runs\/[^/]+\/mcp\/servers\/[^/]+\/auth\/start$/.test(path)) {
853
+ const runId = routeId(path, /^\/runs\/([^/]+)\//, "runId");
854
+ return dispatchToAgent(await resolveRunAgent(runId));
855
+ }
856
+ if (request.method === "POST" && path === "/mcp/callback/redeem") {
857
+ throw new BffInputError(409, "MCP_CALLBACK_DIRECT", "Server-embed MCP callbacks complete in the authorization popup.");
858
+ }
859
+ if (request.method === "POST" && path === "/session/revoke") {
860
+ const headers = await options.revokePrincipal?.(request, resolution.principal);
861
+ return mergeHeaders(new Response(null, { status: 204, ...headers ? { headers } : {} }), resolution.responseHeaders);
862
+ }
863
+ return mergeHeaders(publicError(404, "NOT_FOUND", "The requested BFF route does not exist."), resolution.responseHeaders);
864
+ } catch (error) {
865
+ if (error instanceof BffInputError) return mergeHeaders(publicError(error.status, error.code, error.message), resolution.responseHeaders);
866
+ return mergeHeaders(errorResponse(error), resolution.responseHeaders);
867
+ }
868
+ };
869
+ }
870
+ function createAgents24BffHandler(options) {
871
+ const resolveAgents = prepareAgents24BffAgentResolution(options);
872
+ let resolvedHandler;
873
+ const resolve = async () => {
874
+ const agents = await resolveAgents();
875
+ return createResolvedAgents24BffHandler({ ...options, agents });
876
+ };
877
+ return async (request) => {
878
+ if (!browserRequestIsSameOrigin(request)) {
879
+ return publicError(403, "BFF_ORIGIN_DENIED", "The BFF accepts same-origin browser requests only.");
880
+ }
881
+ resolvedHandler ||= resolve();
882
+ try {
883
+ return (await resolvedHandler)(request);
884
+ } catch (error) {
885
+ resolvedHandler = void 0;
886
+ return errorResponse(error);
887
+ }
888
+ };
889
+ }
461
890
 
462
891
  exports.createAgents24BffHandler = createAgents24BffHandler;
463
892
  //# sourceMappingURL=bff.cjs.map