@agents24/node 0.2.0 → 0.2.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 (54) hide show
  1. package/README.md +17 -4
  2. package/compatibility.json +10 -10
  3. package/dist/bff.cjs +204 -33
  4. package/dist/bff.cjs.map +1 -1
  5. package/dist/bff.d.cts +5 -3
  6. package/dist/bff.d.ts +5 -3
  7. package/dist/bff.js +204 -33
  8. package/dist/bff.js.map +1 -1
  9. package/dist/{client-6MWalxux.d.cts → client-CnOaaCVH.d.cts} +431 -71
  10. package/dist/{client-6MWalxux.d.ts → client-CnOaaCVH.d.ts} +431 -71
  11. package/dist/index.cjs +187 -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 +187 -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 +4 -2
  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 +5 -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 +384 -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-07
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 the deployment ID, and resolve authenticated customers as `{ issuer, subject }`. 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, deployment, and optional exact Agent version on the server. `npm create agents24-app@latest` generates the complete Hono/Vite arrangement.
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": "13f59fd11fc838b1f7a51afe15afcdb6f1429fed94e37a1341c97061882e57d2",
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": "13f59fd11fc838b1f7a51afe15afcdb6f1429fed94e37a1341c97061882e57d2",
19
+ "runtime": "run-stream.v3"
20
20
  },
21
21
  "@agents24/node": {
22
- "contract": "90a64d2df2facc3c4510edc7a0762cb86a336578667d3bc6cfeb7ae035133e00",
22
+ "contract": "13f59fd11fc838b1f7a51afe15afcdb6f1429fed94e37a1341c97061882e57d2",
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": "13f59fd11fc838b1f7a51afe15afcdb6f1429fed94e37a1341c97061882e57d2",
31
31
  "protocolOwner": "@agents24/client"
32
32
  },
33
33
  "agents24": {
34
- "contract": "90a64d2df2facc3c4510edc7a0762cb86a336578667d3bc6cfeb7ae035133e00",
34
+ "contract": "13f59fd11fc838b1f7a51afe15afcdb6f1429fed94e37a1341c97061882e57d2",
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": 78,
52
+ "python": 69
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,14 +244,15 @@ 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
  }
149
252
  function createAgents24BffHandler(options) {
150
253
  const agentId = normalized(options.agentId, "agentId");
151
254
  const agentVersionId = stringValue(options.agentVersionId);
255
+ const deploymentId = normalized(options.deploymentId, "deploymentId");
152
256
  const basePath = normalizeBasePath(options.basePath);
153
257
  const embed = options.client.embed;
154
258
  return async (request) => {
@@ -156,29 +260,51 @@ function createAgents24BffHandler(options) {
156
260
  if (url.pathname !== basePath && !url.pathname.startsWith(`${basePath}/`)) {
157
261
  return publicError(404, "NOT_FOUND", "The requested BFF route does not exist.");
158
262
  }
263
+ if (!browserRequestIsSameOrigin(request)) {
264
+ return publicError(403, "BFF_ORIGIN_DENIED", "The BFF accepts same-origin browser requests only.");
265
+ }
159
266
  const path = url.pathname.slice(basePath.length) || "/";
160
267
  let resolution;
161
268
  try {
162
269
  resolution = await options.resolvePrincipal(request);
163
- resolution.principal.externalUserId = normalized(resolution.principal.externalUserId, "externalUserId");
270
+ resolution.principal.issuer = normalized(resolution.principal.issuer, "issuer");
271
+ resolution.principal.subject = normalized(resolution.principal.subject, "subject");
164
272
  } catch {
165
273
  return publicError(401, "PRINCIPAL_REQUIRED", "A valid application session is required.");
166
274
  }
167
275
  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
276
  const idempotencyKey = stringValue(request.headers.get("idempotency-key"));
177
277
  try {
278
+ let customerPrincipalRef = "";
279
+ if (!(request.method === "GET" && (path === "/bootstrap" || path === "/resource-policy"))) {
280
+ const entitlement = await options.client.resourcePolicies.getEffectiveCustomerPolicy({
281
+ issuer: principal.issuer,
282
+ subject: principal.subject,
283
+ deployment_id: deploymentId
284
+ });
285
+ customerPrincipalRef = normalized(
286
+ String(entitlement.customer_principal_ref || ""),
287
+ "customerPrincipalRef"
288
+ );
289
+ }
290
+ const identity = {
291
+ external_user_id: customerPrincipalRef,
292
+ ...principal.externalSessionId ? { external_session_id: principal.externalSessionId } : {}
293
+ };
294
+ const threadIdentity = {
295
+ externalUserId: customerPrincipalRef,
296
+ ...principal.externalSessionId ? { externalSessionId: principal.externalSessionId } : {}
297
+ };
178
298
  let response;
179
299
  if (request.method === "GET" && path === "/bootstrap") {
180
300
  const raw = await embed.getRuntimeBootstrap(agentId, agentVersionId);
181
301
  response = json(bootstrapDocument(raw, agentVersionId));
302
+ } else if (request.method === "GET" && path === "/resource-policy") {
303
+ response = json(await options.client.resourcePolicies.getEffectiveCustomerPolicy({
304
+ issuer: principal.issuer,
305
+ subject: principal.subject,
306
+ deployment_id: deploymentId
307
+ }));
182
308
  } else if (request.method === "POST" && path === "/chat/stream") {
183
309
  const body = await requestJson(request);
184
310
  const input = stringValue(body.input);
@@ -199,15 +325,45 @@ function createAgents24BffHandler(options) {
199
325
  });
200
326
  } else if (request.method === "POST" && /^\/runs\/[^/]+\/attach$/.test(path)) {
201
327
  const runId = routeId(path, /^\/runs\/([^/]+)\/attach$/, "runId");
328
+ const body = await requestJson(request);
329
+ const cursor = body.cursor;
330
+ if (cursor !== void 0 && (!Number.isInteger(cursor) || Number(cursor) < 0)) {
331
+ throw new BffInputError(400, "INVALID_CURSOR", "cursor must be a non-negative integer.");
332
+ }
202
333
  response = sseResponse(async (write) => {
203
- await embed.attachAgentRun(agentId, runId, identity, (event) => write(event), {
334
+ await embed.attachAgentRun(agentId, runId, {
335
+ ...identity,
336
+ ...cursor === void 0 ? {} : { cursor: Number(cursor) }
337
+ }, (event) => write(event), {
204
338
  idempotencyKey,
205
339
  signal: request.signal
206
340
  });
207
341
  });
208
342
  } else if (request.method === "POST" && /^\/runs\/[^/]+\/cancel$/.test(path)) {
209
343
  const runId = routeId(path, /^\/runs\/([^/]+)\/cancel$/, "runId");
210
- response = json(await embed.cancelAgentRun(agentId, runId, threadIdentity));
344
+ response = json(await embed.cancelAgentRun(agentId, runId, threadIdentity, { idempotencyKey }));
345
+ } else if (request.method === "PUT" && /^\/runs\/[^/]+\/feedback$/.test(path)) {
346
+ const runId = routeId(path, /^\/runs\/([^/]+)\/feedback$/, "runId");
347
+ const body = await requestJson(request);
348
+ const rating = body.rating === null ? null : stringValue(body.rating);
349
+ const reasons = /* @__PURE__ */ new Set(["incorrect", "irrelevant", "incomplete", "unsafe", "citation_issue", "other"]);
350
+ const reason = stringValue(body.reason);
351
+ const comment = stringValue(body.comment);
352
+ if (rating !== null && rating !== "like" && rating !== "dislike") {
353
+ throw new BffInputError(422, "INVALID_FEEDBACK", "rating must be like, dislike, or null.");
354
+ }
355
+ if (reason && !reasons.has(reason)) {
356
+ throw new BffInputError(422, "INVALID_FEEDBACK", "The feedback reason is invalid.");
357
+ }
358
+ if (comment && comment.length > 1e3) {
359
+ throw new BffInputError(422, "INVALID_FEEDBACK", "The feedback comment is too long.");
360
+ }
361
+ response = json(await embed.setAgentRunFeedback(agentId, runId, {
362
+ ...identity,
363
+ rating,
364
+ ...rating === "dislike" && reason ? { reason } : {},
365
+ ...rating === "dislike" && comment ? { comment } : {}
366
+ }));
211
367
  } else if (request.method === "GET" && path === "/threads") {
212
368
  const skip = Number(url.searchParams.get("skip") || 0);
213
369
  const limit = Number(url.searchParams.get("limit") || 20);
@@ -217,27 +373,29 @@ function createAgents24BffHandler(options) {
217
373
  limit: Number.isFinite(limit) ? limit : 20
218
374
  }));
219
375
  } else if (request.method === "GET" && path === "/threads/events") {
220
- const cursor = Number(url.searchParams.get("cursor"));
376
+ const rawCursor = url.searchParams.get("cursor");
377
+ const cursor = rawCursor === null ? void 0 : Number(rawCursor);
221
378
  response = sseResponse(async (write) => {
222
379
  await embed.subscribeAgentThreadEvents(agentId, {
223
380
  ...threadIdentity,
224
- ...Number.isFinite(cursor) ? { cursor } : {},
381
+ ...cursor !== void 0 && Number.isFinite(cursor) ? { cursor } : {},
225
382
  signal: request.signal
226
383
  }, (event) => write(event));
227
384
  });
228
385
  } else if (/^\/threads\/[^/]+$/.test(path) && request.method === "GET") {
229
386
  const threadId = routeId(path, /^\/threads\/([^/]+)$/, "threadId");
230
- const before = Number(url.searchParams.get("before_turn_index"));
231
- const limit = Number(url.searchParams.get("limit"));
387
+ const rawBefore = url.searchParams.get("before_turn_index");
388
+ const rawLimit = url.searchParams.get("limit");
389
+ const before = rawBefore === null ? void 0 : Number(rawBefore);
390
+ const limit = rawLimit === null ? void 0 : Number(rawLimit);
232
391
  response = json(await embed.getAgentThread(agentId, threadId, {
233
392
  ...threadIdentity,
234
- ...Number.isFinite(before) ? { beforeTurnIndex: before } : {},
235
- ...Number.isFinite(limit) ? { limit } : {},
236
- includeRunEvents: url.searchParams.get("include_run_events") === "true"
393
+ ...before !== void 0 && Number.isFinite(before) ? { beforeTurnIndex: before } : {},
394
+ ...limit !== void 0 && Number.isFinite(limit) ? { limit } : {}
237
395
  }));
238
396
  } else if (/^\/threads\/[^/]+$/.test(path) && request.method === "DELETE") {
239
397
  const threadId = routeId(path, /^\/threads\/([^/]+)$/, "threadId");
240
- response = json(await embed.deleteAgentThread(agentId, threadId, threadIdentity));
398
+ response = json(await embed.deleteAgentThread(agentId, threadId, threadIdentity, { idempotencyKey }));
241
399
  } else if (request.method === "POST" && path === "/attachments/upload") {
242
400
  const form = await request.formData();
243
401
  const files = form.getAll("files").filter((entry) => entry instanceof File);
@@ -248,6 +406,18 @@ function createAgents24BffHandler(options) {
248
406
  ...stringValue(form.get("thread_id")) ? { threadId: stringValue(form.get("thread_id")) } : {},
249
407
  idempotencyKey
250
408
  }));
409
+ } else if (request.method === "POST" && /^\/attachments\/[^/]+\/content-access$/.test(path)) {
410
+ const attachmentId = routeId(path, /^\/attachments\/([^/]+)\/content-access$/, "attachmentId");
411
+ const body = await requestJson(request);
412
+ const disposition = stringValue(body.disposition);
413
+ if (disposition !== "inline" && disposition !== "attachment") {
414
+ throw new BffInputError(400, "INVALID_DISPOSITION", "Attachment disposition is invalid.");
415
+ }
416
+ response = json(await embed.createAttachmentContentAccess(agentId, attachmentId, {
417
+ external_user_id: threadIdentity.externalUserId,
418
+ ...threadIdentity.externalSessionId ? { external_session_id: threadIdentity.externalSessionId } : {},
419
+ disposition
420
+ }));
251
421
  } else if (request.method === "POST" && /^\/runs\/[^/]+\/hitl\/[^/]+\/resume$/.test(path)) {
252
422
  const match = /^\/runs\/([^/]+)\/hitl\/([^/]+)\/resume$/.exec(path);
253
423
  const runId = routeId(path, /^\/runs\/([^/]+)\/hitl\//, "runId");
@@ -258,7 +428,8 @@ function createAgents24BffHandler(options) {
258
428
  schema_version: "agents24.hitl.resume.v2",
259
429
  interrupt_id: normalized(interruptId, "interruptId"),
260
430
  action: normalized(String(body.action || ""), "action"),
261
- ...stringValue(body.comment) ? { comment: stringValue(body.comment) } : {}
431
+ ...stringValue(body.comment) ? { comment: stringValue(body.comment) } : {},
432
+ ...Array.isArray(body.answers) ? { answers: body.answers } : {}
262
433
  }, { idempotencyKey }));
263
434
  } else if (request.method === "POST" && /^\/runs\/[^/]+\/mcp\/servers\/[^/]+\/auth\/start$/.test(path)) {
264
435
  const match = /^\/runs\/([^/]+)\/mcp\/servers\/([^/]+)\/auth\/start$/.exec(path);