@agents24/node 0.4.0 → 0.5.1

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