@agents24/node 0.2.0 → 0.3.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 (54) hide show
  1. package/README.md +17 -4
  2. package/compatibility.json +10 -10
  3. package/dist/bff.cjs +202 -33
  4. package/dist/bff.cjs.map +1 -1
  5. package/dist/bff.d.cts +3 -2
  6. package/dist/bff.d.ts +3 -2
  7. package/dist/bff.js +202 -33
  8. package/dist/bff.js.map +1 -1
  9. package/dist/{client-6MWalxux.d.cts → client-Rmk3dh_D.d.cts} +437 -71
  10. package/dist/{client-6MWalxux.d.ts → client-Rmk3dh_D.d.ts} +437 -71
  11. package/dist/index.cjs +194 -45
  12. package/dist/index.cjs.map +1 -1
  13. package/dist/index.d.cts +3 -3
  14. package/dist/index.d.ts +3 -3
  15. package/dist/index.js +194 -45
  16. package/dist/index.js.map +1 -1
  17. package/dist/server.cjs +52 -6
  18. package/dist/server.cjs.map +1 -1
  19. package/dist/server.d.cts +14 -2
  20. package/dist/server.d.ts +14 -2
  21. package/dist/server.js +50 -7
  22. package/dist/server.js.map +1 -1
  23. package/dist/types/agents.d.ts +5 -4
  24. package/dist/types/agents.d.ts.map +1 -1
  25. package/dist/types/bff.d.ts +2 -1
  26. package/dist/types/bff.d.ts.map +1 -1
  27. package/dist/types/builders.d.ts +2 -0
  28. package/dist/types/builders.d.ts.map +1 -1
  29. package/dist/types/client-runtime-admin.d.ts +1 -1
  30. package/dist/types/client.d.ts +4 -0
  31. package/dist/types/client.d.ts.map +1 -1
  32. package/dist/types/embed.d.ts +6 -3
  33. package/dist/types/embed.d.ts.map +1 -1
  34. package/dist/types/errors.d.ts +10 -2
  35. package/dist/types/errors.d.ts.map +1 -1
  36. package/dist/types/generated/instructions.d.ts +8 -0
  37. package/dist/types/generated/instructions.d.ts.map +1 -0
  38. package/dist/types/generated/manifest.d.ts +1 -1
  39. package/dist/types/generated/manifest.d.ts.map +1 -1
  40. package/dist/types/generated/resourceBundles.d.ts +4 -1
  41. package/dist/types/generated/resourceBundles.d.ts.map +1 -1
  42. package/dist/types/generated/resourcePolicies.d.ts +14 -0
  43. package/dist/types/generated/resourcePolicies.d.ts.map +1 -0
  44. package/dist/types/http.d.ts +0 -2
  45. package/dist/types/http.d.ts.map +1 -1
  46. package/dist/types/index.d.ts +1 -1
  47. package/dist/types/index.d.ts.map +1 -1
  48. package/dist/types/runs.d.ts +4 -2
  49. package/dist/types/runs.d.ts.map +1 -1
  50. package/dist/types/server.d.ts +13 -1
  51. package/dist/types/server.d.ts.map +1 -1
  52. package/dist/types/types.d.ts +389 -60
  53. package/dist/types/types.d.ts.map +1 -1
  54. package/package.json +2 -2
package/README.md CHANGED
@@ -1,17 +1,17 @@
1
1
  # `@agents24/node`
2
2
 
3
- Last Updated: 2026-07-19
3
+ Last Updated: 2026-08-12
4
4
 
5
5
  Trusted-server Agents24 SDK for organization-authenticated control-plane, server embed, authoring, artifact, client-deployment, and client-session mint operations. It supports Node 22 and 24 with ESM and CJS entrypoints.
6
6
 
7
+ Runtime attachment uploads return durable metadata. Use `agents.createAttachmentContentAccess(...)` for internal attachments or `embed.createAttachmentContentAccess(...)` for server-embed attachments to mint a short-lived private inline or direct-download URL.
8
+
7
9
  ```ts
8
10
  import { Agents24 } from "@agents24/node";
9
11
 
10
12
  const agents24 = new Agents24({
11
13
  baseUrl: process.env.AGENTS24_BASE_URL!,
12
14
  apiKey: process.env.AGENTS24_API_KEY!,
13
- organizationId: process.env.AGENTS24_ORGANIZATION_ID!,
14
- projectId: process.env.AGENTS24_PROJECT_ID!,
15
15
  });
16
16
  ```
17
17
 
@@ -31,6 +31,10 @@ const session = await agents24.clientSessions.createBackendSession(
31
31
 
32
32
  Administer deployments through `agents24.clientDeployments`; use `agents24.agents`, `agents24.runs`, and `agents24.embed` for trusted Agent operations. End-user token networking remains in `@agents24/client`.
33
33
 
34
+ HITL V2 includes `respond` for Agent-requested user input. Its payload contains ordered answers with `question_id`, `selected_option_ids`, and optional `custom_text`; use the existing resume method and then reattach to the same run.
35
+
36
+ Deployment administration uses reversible `lifecycle_status: "active" | "inactive"` updates and irreversible `clientDeployments.delete(...)`. Active deployments require a Resource Policy; removing it makes the deployment inactive and invalidates issued sessions. There is no `revoke()` method.
37
+
34
38
  New deployments follow the latest published Agent version by default. Use
35
39
  `version_policy: { mode: "pinned", version_id: "..." }` for a fixed client deployment.
36
40
  Server embed requests accept an optional `agent_version_id`; omission resolves latest published.
@@ -43,9 +47,18 @@ import { defineArtifact, tool } from "@agents24/node/artifacts";
43
47
  ```
44
48
 
45
49
  Self-hosted Node artifact servers use `@agents24/node/server`. That server subpath does not re-export the declaration DSL.
50
+ `createArtifactServer` exposes manifest, health, connection-verification, and
51
+ invocation routes. Registration uses
52
+ `POST /.well-known/agents24/artifact/verify`; signed servers prove possession
53
+ of the configured secret without transmitting it, while `authMode: "none"`
54
+ servers return the matching unsigned challenge with no proof.
46
55
 
47
56
  This package is server-only. Never expose an organization API key to browser code. Persistent or native client-session enrollment requires DPoP; pass the proof as `dpopProof` in the mint request options and read a nonce challenge from `Agents24SDKError.responseHeaders`.
48
57
 
49
58
  ## Same-origin BFF
50
59
 
51
- `@agents24/node/bff` exports `createAgents24BffHandler(...)`. Mount its returned Fetch handler at a same-origin path, supply an authenticated external principal through `resolvePrincipal`, and pair it with `createBffAgentClient` from `@agents24/client/bff`. The bridge uses only `client.embed`; it keeps the API key, Agent ID, and optional exact Agent version on the server. `npm create agents24-app@latest` generates the complete Hono/Vite arrangement.
60
+ `@agents24/node/bff` exports `createAgents24BffHandler(...)`. Mount its returned Fetch handler at a same-origin path, provide an API-key client plus Agent ID, and resolve a stable customer `subject`. Supply an optional `issuer` only when an authenticated customer must receive organization-wide governance across Agents or applications. Pair it with `createBffAgentClient` from `@agents24/client/bff`. The handler rejects cross-site browser requests before principal resolution, forwards durable attach cursors, and preserves caller idempotency keys for retryable mutations. The bridge keeps the API key, Agent, and optional exact Agent version on the server; it does not use a Client Deployment. `npm create agents24-app@latest` generates the complete Hono/Vite arrangement, including an auto-generated cookie secret for its default guest session.
61
+
62
+ ## Customer Resource Policies
63
+
64
+ `agents24.resourcePolicies` lists dashboard-authored Policy Sets and manages customer assignment, effective projection, quota schedules, and explicit resets. Raw `{ issuer, subject }` identity is sent only in request bodies. Supply `{ idempotencyKey }` to mutations. Organization keys need `resource_policies.read`, plus `resource_policies.write` for mutation methods.
@@ -1,5 +1,5 @@
1
1
  {
2
- "contractHash": "90a64d2df2facc3c4510edc7a0762cb86a336578667d3bc6cfeb7ae035133e00",
2
+ "contractHash": "a409af6996d10a8d94e0f6332c9a381c3535a9c8511314b6c42b237d7e057029",
3
3
  "contractVersion": "0.2.0",
4
4
  "corpus": "conformance/v1/corpus.json",
5
5
  "generatedBy": "@agents24/sdk-contract",
@@ -15,11 +15,11 @@
15
15
  "reactDom": ">=19 <20"
16
16
  },
17
17
  "@agents24/client": {
18
- "contract": "90a64d2df2facc3c4510edc7a0762cb86a336578667d3bc6cfeb7ae035133e00",
19
- "runtime": "run-stream.v2"
18
+ "contract": "a409af6996d10a8d94e0f6332c9a381c3535a9c8511314b6c42b237d7e057029",
19
+ "runtime": "run-stream.v3"
20
20
  },
21
21
  "@agents24/node": {
22
- "contract": "90a64d2df2facc3c4510edc7a0762cb86a336578667d3bc6cfeb7ae035133e00",
22
+ "contract": "a409af6996d10a8d94e0f6332c9a381c3535a9c8511314b6c42b237d7e057029",
23
23
  "node": "^22.0.0 || ^24.0.0"
24
24
  },
25
25
  "@agents24/react": {
@@ -27,18 +27,18 @@
27
27
  "react": ">=19 <20"
28
28
  },
29
29
  "@talmudpedia/runtime-sdk": {
30
- "contract": "90a64d2df2facc3c4510edc7a0762cb86a336578667d3bc6cfeb7ae035133e00",
30
+ "contract": "a409af6996d10a8d94e0f6332c9a381c3535a9c8511314b6c42b237d7e057029",
31
31
  "protocolOwner": "@agents24/client"
32
32
  },
33
33
  "agents24": {
34
- "contract": "90a64d2df2facc3c4510edc7a0762cb86a336578667d3bc6cfeb7ae035133e00",
34
+ "contract": "a409af6996d10a8d94e0f6332c9a381c3535a9c8511314b6c42b237d7e057029",
35
35
  "python": ">=3.10"
36
36
  }
37
37
  },
38
38
  "protocols": {
39
39
  "runtime": {
40
40
  "minimum": "2.0.0",
41
- "wire": "run-stream.v2"
41
+ "wire": "run-stream.v3"
42
42
  },
43
43
  "threadSummary": {
44
44
  "minimum": "1.0.0",
@@ -47,8 +47,8 @@
47
47
  },
48
48
  "schemaVersion": "agents24.compatibility.v1",
49
49
  "targetOperationCounts": {
50
- "client": 19,
51
- "node": 60,
52
- "python": 55
50
+ "client": 22,
51
+ "node": 79,
52
+ "python": 70
53
53
  }
54
54
  }
package/dist/bff.cjs CHANGED
@@ -8,16 +8,37 @@ var crypto = require('crypto');
8
8
  var Agents24SDKError = class extends Error {
9
9
  kind;
10
10
  status;
11
- details;
11
+ failure;
12
12
  responseHeaders;
13
13
  constructor(message, options) {
14
14
  super(message, { cause: options.cause });
15
15
  this.name = "Agents24SDKError";
16
16
  this.kind = options.kind;
17
17
  this.status = options.status;
18
- this.details = options.details;
18
+ this.failure = options.failure;
19
19
  this.responseHeaders = options.responseHeaders;
20
20
  }
21
+ get code() {
22
+ return this.failure?.code;
23
+ }
24
+ get category() {
25
+ return this.failure?.category;
26
+ }
27
+ get failureId() {
28
+ return this.failure?.failure_id;
29
+ }
30
+ get requestId() {
31
+ return this.failure?.request_id ?? void 0;
32
+ }
33
+ get retryable() {
34
+ return this.failure?.retryable ?? false;
35
+ }
36
+ get retryAfterMs() {
37
+ return this.failure?.retry_after_ms ?? void 0;
38
+ }
39
+ get details() {
40
+ return this.failure?.details ?? {};
41
+ }
21
42
  };
22
43
 
23
44
  // src/bff.ts
@@ -32,15 +53,98 @@ function normalizeBasePath(value) {
32
53
  if (!path.startsWith("/") || /[\r\n\0?#]/.test(path)) throw new Error("basePath must be an absolute path.");
33
54
  return path;
34
55
  }
35
- function publicError(status, code, message, retryable = false) {
36
- return Response.json({ code, message, request_id: crypto.randomUUID(), retryable }, {
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
+ }
67
+ function publicError(status, code, message, retryable = false, violations) {
68
+ const category = status === 401 ? "authentication" : status === 403 ? "authorization" : status === 404 ? "not_found" : status === 409 ? "conflict" : status === 429 ? "rate_limit" : status >= 500 ? "internal" : "validation";
69
+ return Response.json({
70
+ schema_version: "agents24.failure.v1",
71
+ failure_id: crypto.randomUUID(),
72
+ code,
73
+ category,
74
+ message,
75
+ request_id: crypto.randomUUID(),
76
+ retryable,
77
+ retry_after_ms: null,
78
+ details: {}
79
+ }, {
80
+ status,
81
+ headers: { "Cache-Control": "no-store", "Content-Type": "application/json; charset=utf-8" }
82
+ });
83
+ }
84
+ function safeResourcePolicyError(details) {
85
+ if (!details || typeof details !== "object" || Array.isArray(details)) return null;
86
+ const outer = details;
87
+ const candidate = outer.schema_version === "agents24.failure.v1" ? outer : outer.detail && typeof outer.detail === "object" && !Array.isArray(outer.detail) ? outer.detail : outer;
88
+ const code = typeof candidate.code === "string" ? candidate.code : "";
89
+ if (!["RESOURCE_POLICY_ACCESS_DENIED", "RESOURCE_POLICY_QUOTA_EXCEEDED", "RESOURCE_POLICY_CONFIGURATION_ERROR"].includes(code)) return null;
90
+ const failureId = typeof candidate.failure_id === "string" && candidate.failure_id.trim() ? candidate.failure_id : "";
91
+ if (!failureId) return null;
92
+ const message = typeof candidate.message === "string" && candidate.message.trim() ? candidate.message.trim() : "The resource policy prevented this request.";
93
+ const allowedViolationKeys = /* @__PURE__ */ new Set([
94
+ "policy_subject_type",
95
+ "rule_type",
96
+ "aggregation_scope",
97
+ "resource_type",
98
+ "resource_name",
99
+ "quota_unit",
100
+ "quota_window",
101
+ "limit",
102
+ "remaining",
103
+ "resets_at"
104
+ ]);
105
+ const candidateDetails = candidate.details && typeof candidate.details === "object" ? candidate.details : candidate;
106
+ const violations = Array.isArray(candidateDetails.violations) ? candidateDetails.violations.flatMap((item) => {
107
+ if (!item || typeof item !== "object" || Array.isArray(item)) return [];
108
+ return [Object.fromEntries(
109
+ Object.entries(item).filter(([key, value]) => allowedViolationKeys.has(key) && ["string", "number"].includes(typeof value))
110
+ )];
111
+ }).filter((item) => Object.keys(item).length > 0).slice(0, 20) : void 0;
112
+ return {
113
+ failureId,
114
+ code,
115
+ category: code === "RESOURCE_POLICY_QUOTA_EXCEEDED" ? "quota" : "policy",
116
+ message,
117
+ requestId: typeof candidate.request_id === "string" ? candidate.request_id : null,
118
+ retryable: candidate.retryable === true,
119
+ retryAfterMs: typeof candidate.retry_after_ms === "number" && candidate.retry_after_ms >= 0 ? candidate.retry_after_ms : null,
120
+ ...violations?.length ? { violations } : {}
121
+ };
122
+ }
123
+ function resourcePolicyFailureResponse(status, failure) {
124
+ return Response.json({
125
+ schema_version: "agents24.failure.v1",
126
+ failure_id: failure.failureId,
127
+ code: failure.code,
128
+ category: failure.category,
129
+ message: failure.message,
130
+ retryable: failure.retryable,
131
+ request_id: failure.requestId,
132
+ retry_after_ms: failure.retryAfterMs,
133
+ details: failure.violations?.length ? { violations: failure.violations } : {}
134
+ }, {
37
135
  status,
38
136
  headers: { "Cache-Control": "no-store", "Content-Type": "application/json; charset=utf-8" }
39
137
  });
40
138
  }
41
139
  function errorResponse(error) {
42
- if (error instanceof Agents24SDKError) {
43
- const status = error.status && error.status >= 400 && error.status <= 599 ? error.status : 502;
140
+ const isSdkError = error instanceof Agents24SDKError || error instanceof Error && error.name === "Agents24SDKError" && "kind" in error && ["protocol", "network", "http"].includes(String(error.kind));
141
+ if (isSdkError) {
142
+ const sdkError = error;
143
+ const status = sdkError.status && sdkError.status >= 400 && sdkError.status <= 599 ? sdkError.status : 502;
144
+ const policyError = safeResourcePolicyError(sdkError.failure);
145
+ if (policyError) {
146
+ return resourcePolicyFailureResponse(status, policyError);
147
+ }
44
148
  return publicError(status, `UPSTREAM_${status}`, "The Agent request failed.", status >= 500);
45
149
  }
46
150
  return publicError(500, "BFF_REQUEST_FAILED", "The Agent request could not be completed.", true);
@@ -112,22 +216,21 @@ function sseResponse(execute, headers) {
112
216
  void execute(write).catch(async (error) => {
113
217
  const message = "The Agent stream failed.";
114
218
  const event = {
115
- version: "run-stream.v2",
219
+ version: "run-stream.v3",
116
220
  seq: Number.MAX_SAFE_INTEGER,
117
221
  ts: (/* @__PURE__ */ new Date()).toISOString(),
118
222
  event: "run.failed",
119
223
  run_id: "bff-stream-error",
120
224
  stage: "run",
121
- payload: { error: message },
122
- diagnostics: [{ message }]
225
+ payload: { error: message }
123
226
  };
124
227
  await write(event).catch(() => void 0);
125
228
  }).finally(() => writer.close().catch(() => void 0));
126
229
  return new Response(stream.readable, { headers: sseHeaders(headers) });
127
230
  }
128
231
  function bootstrapDocument(raw, agentVersionId) {
129
- const selector = objectValue(raw.model_selector);
130
- const selectable = Boolean(selector.selectable);
232
+ const features = objectValue(raw.features);
233
+ const selectable = objectValue(features.models).selectable === true;
131
234
  const resolvedVersionId = stringValue(raw.agent_version_id) || agentVersionId || "";
132
235
  const versionNumber = Number(raw.agent_version_number || 0);
133
236
  return {
@@ -141,8 +244,8 @@ function bootstrapDocument(raw, agentVersionId) {
141
244
  policy_epoch: 0
142
245
  },
143
246
  authentication: { modes: ["server_embed"], access_token_lifetime_seconds: 0, jwks_path: "" },
144
- capabilities: ["attachments.write", ...selectable ? ["models.select"] : []],
145
- features: { models: selector },
247
+ capabilities: ["attachments.read", "attachments.write", "feedback.write", ...selectable ? ["models.select"] : []],
248
+ features,
146
249
  native_profiles: []
147
250
  };
148
251
  }
@@ -156,29 +259,50 @@ function createAgents24BffHandler(options) {
156
259
  if (url.pathname !== basePath && !url.pathname.startsWith(`${basePath}/`)) {
157
260
  return publicError(404, "NOT_FOUND", "The requested BFF route does not exist.");
158
261
  }
262
+ if (!browserRequestIsSameOrigin(request)) {
263
+ return publicError(403, "BFF_ORIGIN_DENIED", "The BFF accepts same-origin browser requests only.");
264
+ }
159
265
  const path = url.pathname.slice(basePath.length) || "/";
160
266
  let resolution;
161
267
  try {
162
268
  resolution = await options.resolvePrincipal(request);
163
- resolution.principal.externalUserId = normalized(resolution.principal.externalUserId, "externalUserId");
269
+ resolution.principal.subject = normalized(resolution.principal.subject, "subject");
270
+ if (resolution.principal.issuer !== void 0) {
271
+ resolution.principal.issuer = normalized(resolution.principal.issuer, "issuer");
272
+ }
164
273
  } catch {
165
274
  return publicError(401, "PRINCIPAL_REQUIRED", "A valid application session is required.");
166
275
  }
167
276
  const principal = resolution.principal;
168
- const identity = {
169
- external_user_id: principal.externalUserId,
170
- ...principal.externalSessionId ? { external_session_id: principal.externalSessionId } : {}
171
- };
172
- const threadIdentity = {
173
- externalUserId: principal.externalUserId,
174
- ...principal.externalSessionId ? { externalSessionId: principal.externalSessionId } : {}
175
- };
176
277
  const idempotencyKey = stringValue(request.headers.get("idempotency-key"));
177
278
  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
+ }
288
+ const identity = {
289
+ external_user_id: customerPrincipalRef,
290
+ ...principal.externalSessionId ? { external_session_id: principal.externalSessionId } : {}
291
+ };
292
+ const threadIdentity = {
293
+ externalUserId: customerPrincipalRef,
294
+ ...principal.externalSessionId ? { externalSessionId: principal.externalSessionId } : {}
295
+ };
178
296
  let response;
179
297
  if (request.method === "GET" && path === "/bootstrap") {
180
298
  const raw = await embed.getRuntimeBootstrap(agentId, agentVersionId);
181
299
  response = json(bootstrapDocument(raw, agentVersionId));
300
+ } else if (request.method === "GET" && path === "/resource-policy") {
301
+ 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 } : {}
305
+ }));
182
306
  } else if (request.method === "POST" && path === "/chat/stream") {
183
307
  const body = await requestJson(request);
184
308
  const input = stringValue(body.input);
@@ -199,15 +323,45 @@ function createAgents24BffHandler(options) {
199
323
  });
200
324
  } else if (request.method === "POST" && /^\/runs\/[^/]+\/attach$/.test(path)) {
201
325
  const runId = routeId(path, /^\/runs\/([^/]+)\/attach$/, "runId");
326
+ const body = await requestJson(request);
327
+ const cursor = body.cursor;
328
+ if (cursor !== void 0 && (!Number.isInteger(cursor) || Number(cursor) < 0)) {
329
+ throw new BffInputError(400, "INVALID_CURSOR", "cursor must be a non-negative integer.");
330
+ }
202
331
  response = sseResponse(async (write) => {
203
- await embed.attachAgentRun(agentId, runId, identity, (event) => write(event), {
332
+ await embed.attachAgentRun(agentId, runId, {
333
+ ...identity,
334
+ ...cursor === void 0 ? {} : { cursor: Number(cursor) }
335
+ }, (event) => write(event), {
204
336
  idempotencyKey,
205
337
  signal: request.signal
206
338
  });
207
339
  });
208
340
  } else if (request.method === "POST" && /^\/runs\/[^/]+\/cancel$/.test(path)) {
209
341
  const runId = routeId(path, /^\/runs\/([^/]+)\/cancel$/, "runId");
210
- response = json(await embed.cancelAgentRun(agentId, runId, threadIdentity));
342
+ response = json(await embed.cancelAgentRun(agentId, runId, threadIdentity, { idempotencyKey }));
343
+ } else if (request.method === "PUT" && /^\/runs\/[^/]+\/feedback$/.test(path)) {
344
+ const runId = routeId(path, /^\/runs\/([^/]+)\/feedback$/, "runId");
345
+ const body = await requestJson(request);
346
+ const rating = body.rating === null ? null : stringValue(body.rating);
347
+ const reasons = /* @__PURE__ */ new Set(["incorrect", "irrelevant", "incomplete", "unsafe", "citation_issue", "other"]);
348
+ const reason = stringValue(body.reason);
349
+ const comment = stringValue(body.comment);
350
+ if (rating !== null && rating !== "like" && rating !== "dislike") {
351
+ throw new BffInputError(422, "INVALID_FEEDBACK", "rating must be like, dislike, or null.");
352
+ }
353
+ if (reason && !reasons.has(reason)) {
354
+ throw new BffInputError(422, "INVALID_FEEDBACK", "The feedback reason is invalid.");
355
+ }
356
+ if (comment && comment.length > 1e3) {
357
+ throw new BffInputError(422, "INVALID_FEEDBACK", "The feedback comment is too long.");
358
+ }
359
+ response = json(await embed.setAgentRunFeedback(agentId, runId, {
360
+ ...identity,
361
+ rating,
362
+ ...rating === "dislike" && reason ? { reason } : {},
363
+ ...rating === "dislike" && comment ? { comment } : {}
364
+ }));
211
365
  } else if (request.method === "GET" && path === "/threads") {
212
366
  const skip = Number(url.searchParams.get("skip") || 0);
213
367
  const limit = Number(url.searchParams.get("limit") || 20);
@@ -217,27 +371,29 @@ function createAgents24BffHandler(options) {
217
371
  limit: Number.isFinite(limit) ? limit : 20
218
372
  }));
219
373
  } else if (request.method === "GET" && path === "/threads/events") {
220
- const cursor = Number(url.searchParams.get("cursor"));
374
+ const rawCursor = url.searchParams.get("cursor");
375
+ const cursor = rawCursor === null ? void 0 : Number(rawCursor);
221
376
  response = sseResponse(async (write) => {
222
377
  await embed.subscribeAgentThreadEvents(agentId, {
223
378
  ...threadIdentity,
224
- ...Number.isFinite(cursor) ? { cursor } : {},
379
+ ...cursor !== void 0 && Number.isFinite(cursor) ? { cursor } : {},
225
380
  signal: request.signal
226
381
  }, (event) => write(event));
227
382
  });
228
383
  } else if (/^\/threads\/[^/]+$/.test(path) && request.method === "GET") {
229
384
  const threadId = routeId(path, /^\/threads\/([^/]+)$/, "threadId");
230
- const before = Number(url.searchParams.get("before_turn_index"));
231
- const limit = Number(url.searchParams.get("limit"));
385
+ const rawBefore = url.searchParams.get("before_turn_index");
386
+ const rawLimit = url.searchParams.get("limit");
387
+ const before = rawBefore === null ? void 0 : Number(rawBefore);
388
+ const limit = rawLimit === null ? void 0 : Number(rawLimit);
232
389
  response = json(await embed.getAgentThread(agentId, threadId, {
233
390
  ...threadIdentity,
234
- ...Number.isFinite(before) ? { beforeTurnIndex: before } : {},
235
- ...Number.isFinite(limit) ? { limit } : {},
236
- includeRunEvents: url.searchParams.get("include_run_events") === "true"
391
+ ...before !== void 0 && Number.isFinite(before) ? { beforeTurnIndex: before } : {},
392
+ ...limit !== void 0 && Number.isFinite(limit) ? { limit } : {}
237
393
  }));
238
394
  } else if (/^\/threads\/[^/]+$/.test(path) && request.method === "DELETE") {
239
395
  const threadId = routeId(path, /^\/threads\/([^/]+)$/, "threadId");
240
- response = json(await embed.deleteAgentThread(agentId, threadId, threadIdentity));
396
+ response = json(await embed.deleteAgentThread(agentId, threadId, threadIdentity, { idempotencyKey }));
241
397
  } else if (request.method === "POST" && path === "/attachments/upload") {
242
398
  const form = await request.formData();
243
399
  const files = form.getAll("files").filter((entry) => entry instanceof File);
@@ -248,6 +404,18 @@ function createAgents24BffHandler(options) {
248
404
  ...stringValue(form.get("thread_id")) ? { threadId: stringValue(form.get("thread_id")) } : {},
249
405
  idempotencyKey
250
406
  }));
407
+ } else if (request.method === "POST" && /^\/attachments\/[^/]+\/content-access$/.test(path)) {
408
+ const attachmentId = routeId(path, /^\/attachments\/([^/]+)\/content-access$/, "attachmentId");
409
+ const body = await requestJson(request);
410
+ const disposition = stringValue(body.disposition);
411
+ if (disposition !== "inline" && disposition !== "attachment") {
412
+ throw new BffInputError(400, "INVALID_DISPOSITION", "Attachment disposition is invalid.");
413
+ }
414
+ response = json(await embed.createAttachmentContentAccess(agentId, attachmentId, {
415
+ external_user_id: threadIdentity.externalUserId,
416
+ ...threadIdentity.externalSessionId ? { external_session_id: threadIdentity.externalSessionId } : {},
417
+ disposition
418
+ }));
251
419
  } else if (request.method === "POST" && /^\/runs\/[^/]+\/hitl\/[^/]+\/resume$/.test(path)) {
252
420
  const match = /^\/runs\/([^/]+)\/hitl\/([^/]+)\/resume$/.exec(path);
253
421
  const runId = routeId(path, /^\/runs\/([^/]+)\/hitl\//, "runId");
@@ -258,7 +426,8 @@ function createAgents24BffHandler(options) {
258
426
  schema_version: "agents24.hitl.resume.v2",
259
427
  interrupt_id: normalized(interruptId, "interruptId"),
260
428
  action: normalized(String(body.action || ""), "action"),
261
- ...stringValue(body.comment) ? { comment: stringValue(body.comment) } : {}
429
+ ...stringValue(body.comment) ? { comment: stringValue(body.comment) } : {},
430
+ ...Array.isArray(body.answers) ? { answers: body.answers } : {}
262
431
  }, { idempotencyKey }));
263
432
  } else if (request.method === "POST" && /^\/runs\/[^/]+\/mcp\/servers\/[^/]+\/auth\/start$/.test(path)) {
264
433
  const match = /^\/runs\/([^/]+)\/mcp\/servers\/([^/]+)\/auth\/start$/.exec(path);