@agents24/node 0.1.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 (57) hide show
  1. package/README.md +25 -3
  2. package/compatibility.json +10 -10
  3. package/dist/bff.cjs +466 -0
  4. package/dist/bff.cjs.map +1 -0
  5. package/dist/bff.d.cts +25 -0
  6. package/dist/bff.d.ts +25 -0
  7. package/dist/bff.js +464 -0
  8. package/dist/bff.js.map +1 -0
  9. package/dist/client-CnOaaCVH.d.cts +1018 -0
  10. package/dist/client-CnOaaCVH.d.ts +1018 -0
  11. package/dist/index.cjs +279 -55
  12. package/dist/index.cjs.map +1 -1
  13. package/dist/index.d.cts +4 -561
  14. package/dist/index.d.ts +4 -561
  15. package/dist/index.js +279 -55
  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 +22 -0
  26. package/dist/types/bff.d.ts.map +1 -0
  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 +3 -3
  30. package/dist/types/client-runtime-admin.d.ts.map +1 -1
  31. package/dist/types/client.d.ts +8 -0
  32. package/dist/types/client.d.ts.map +1 -1
  33. package/dist/types/embed.d.ts +6 -4
  34. package/dist/types/embed.d.ts.map +1 -1
  35. package/dist/types/errors.d.ts +10 -2
  36. package/dist/types/errors.d.ts.map +1 -1
  37. package/dist/types/generated/instructions.d.ts +8 -0
  38. package/dist/types/generated/instructions.d.ts.map +1 -0
  39. package/dist/types/generated/manifest.d.ts +1 -1
  40. package/dist/types/generated/manifest.d.ts.map +1 -1
  41. package/dist/types/generated/resourceBundles.d.ts +12 -0
  42. package/dist/types/generated/resourceBundles.d.ts.map +1 -0
  43. package/dist/types/generated/resourcePackages.d.ts +10 -0
  44. package/dist/types/generated/resourcePackages.d.ts.map +1 -0
  45. package/dist/types/generated/resourcePolicies.d.ts +14 -0
  46. package/dist/types/generated/resourcePolicies.d.ts.map +1 -0
  47. package/dist/types/http.d.ts +0 -2
  48. package/dist/types/http.d.ts.map +1 -1
  49. package/dist/types/index.d.ts +1 -1
  50. package/dist/types/index.d.ts.map +1 -1
  51. package/dist/types/runs.d.ts +4 -2
  52. package/dist/types/runs.d.ts.map +1 -1
  53. package/dist/types/server.d.ts +13 -1
  54. package/dist/types/server.d.ts.map +1 -1
  55. package/dist/types/types.d.ts +466 -61
  56. package/dist/types/types.d.ts.map +1 -1
  57. package/package.json +8 -2
package/README.md CHANGED
@@ -1,17 +1,17 @@
1
1
  # `@agents24/node`
2
2
 
3
- Last Updated: 2026-07-16
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,15 @@ 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
+
38
+ New deployments follow the latest published Agent version by default. Use
39
+ `version_policy: { mode: "pinned", version_id: "..." }` for a fixed client deployment.
40
+ Server embed requests accept an optional `agent_version_id`; omission resolves latest published.
41
+ Every run stores the exact resolved version, so later publications do not change historical runs.
42
+
34
43
  Artifact source imports the transport-free declaration DSL:
35
44
 
36
45
  ```ts
@@ -38,5 +47,18 @@ import { defineArtifact, tool } from "@agents24/node/artifacts";
38
47
  ```
39
48
 
40
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.
41
55
 
42
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`.
57
+
58
+ ## Same-origin BFF
59
+
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": "de2b507f5d56de2bf7c4d6739c03617c4b4feef6ae827aaaac5f1d6150ec678e",
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": "de2b507f5d56de2bf7c4d6739c03617c4b4feef6ae827aaaac5f1d6150ec678e",
19
- "runtime": "run-stream.v2"
18
+ "contract": "13f59fd11fc838b1f7a51afe15afcdb6f1429fed94e37a1341c97061882e57d2",
19
+ "runtime": "run-stream.v3"
20
20
  },
21
21
  "@agents24/node": {
22
- "contract": "de2b507f5d56de2bf7c4d6739c03617c4b4feef6ae827aaaac5f1d6150ec678e",
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": "de2b507f5d56de2bf7c4d6739c03617c4b4feef6ae827aaaac5f1d6150ec678e",
30
+ "contract": "13f59fd11fc838b1f7a51afe15afcdb6f1429fed94e37a1341c97061882e57d2",
31
31
  "protocolOwner": "@agents24/client"
32
32
  },
33
33
  "agents24": {
34
- "contract": "de2b507f5d56de2bf7c4d6739c03617c4b4feef6ae827aaaac5f1d6150ec678e",
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": 55,
52
- "python": 55
50
+ "client": 22,
51
+ "node": 78,
52
+ "python": 69
53
53
  }
54
54
  }
package/dist/bff.cjs ADDED
@@ -0,0 +1,466 @@
1
+ 'use strict';
2
+
3
+ var crypto = require('crypto');
4
+
5
+ // src/bff.ts
6
+
7
+ // src/errors.ts
8
+ var Agents24SDKError = class extends Error {
9
+ kind;
10
+ status;
11
+ failure;
12
+ responseHeaders;
13
+ constructor(message, options) {
14
+ super(message, { cause: options.cause });
15
+ this.name = "Agents24SDKError";
16
+ this.kind = options.kind;
17
+ this.status = options.status;
18
+ this.failure = options.failure;
19
+ this.responseHeaders = options.responseHeaders;
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
+ }
42
+ };
43
+
44
+ // src/bff.ts
45
+ var JSON_LIMIT_BYTES = 1048576;
46
+ function normalized(value, field) {
47
+ const result = String(value || "").trim();
48
+ if (!result || /[\r\n\0]/.test(result)) throw new Error(`${field} is required.`);
49
+ return result;
50
+ }
51
+ function normalizeBasePath(value) {
52
+ const path = String(value || "/api/agents24").trim().replace(/\/+$/, "");
53
+ if (!path.startsWith("/") || /[\r\n\0?#]/.test(path)) throw new Error("basePath must be an absolute path.");
54
+ return path;
55
+ }
56
+ function browserRequestIsSameOrigin(request) {
57
+ const fetchSite = String(request.headers.get("sec-fetch-site") || "").trim().toLowerCase();
58
+ if (fetchSite) return fetchSite === "same-origin";
59
+ const rawOrigin = String(request.headers.get("origin") || "").trim();
60
+ if (!rawOrigin) return true;
61
+ try {
62
+ return rawOrigin !== "null" && new URL(rawOrigin).origin === new URL(request.url).origin;
63
+ } catch {
64
+ return false;
65
+ }
66
+ }
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
+ }, {
135
+ status,
136
+ headers: { "Cache-Control": "no-store", "Content-Type": "application/json; charset=utf-8" }
137
+ });
138
+ }
139
+ function errorResponse(error) {
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
+ }
148
+ return publicError(status, `UPSTREAM_${status}`, "The Agent request failed.", status >= 500);
149
+ }
150
+ return publicError(500, "BFF_REQUEST_FAILED", "The Agent request could not be completed.", true);
151
+ }
152
+ function mergeHeaders(response, additional) {
153
+ if (!additional) return response;
154
+ const headers = new Headers(response.headers);
155
+ const extras = new Headers(additional);
156
+ for (const [name, value] of extras) headers.append(name, value);
157
+ return new Response(response.body, { status: response.status, statusText: response.statusText, headers });
158
+ }
159
+ function json(value, headers, status = 200) {
160
+ const responseHeaders = new Headers(headers);
161
+ responseHeaders.set("Cache-Control", "no-store");
162
+ responseHeaders.set("Content-Type", "application/json; charset=utf-8");
163
+ return new Response(JSON.stringify(value), { status, headers: responseHeaders });
164
+ }
165
+ async function requestJson(request) {
166
+ const contentLength = Number(request.headers.get("content-length") || 0);
167
+ if (contentLength > JSON_LIMIT_BYTES) throw new BffInputError(413, "REQUEST_TOO_LARGE", "The request body is too large.");
168
+ const value = await request.json().catch(() => null);
169
+ if (!value || typeof value !== "object" || Array.isArray(value)) {
170
+ throw new BffInputError(400, "INVALID_JSON", "A JSON object is required.");
171
+ }
172
+ return value;
173
+ }
174
+ var BffInputError = class extends Error {
175
+ constructor(status, code, message) {
176
+ super(message);
177
+ this.status = status;
178
+ this.code = code;
179
+ }
180
+ status;
181
+ code;
182
+ };
183
+ function stringValue(value) {
184
+ const result = typeof value === "string" ? value.trim() : "";
185
+ return result || void 0;
186
+ }
187
+ function stringList(value) {
188
+ return Array.isArray(value) ? value.map(stringValue).filter((item) => Boolean(item)) : [];
189
+ }
190
+ function objectValue(value) {
191
+ return value && typeof value === "object" && !Array.isArray(value) ? value : {};
192
+ }
193
+ function routeId(path, pattern, field) {
194
+ const match = pattern.exec(path);
195
+ if (!match?.[1]) throw new BffInputError(404, "NOT_FOUND", "The requested BFF route does not exist.");
196
+ try {
197
+ return normalized(decodeURIComponent(match[1]), field);
198
+ } catch {
199
+ throw new BffInputError(400, "INVALID_PATH", `${field} is invalid.`);
200
+ }
201
+ }
202
+ function sseHeaders(headers) {
203
+ const result = new Headers(headers);
204
+ result.set("Cache-Control", "no-cache, no-transform");
205
+ result.set("Content-Type", "text/event-stream; charset=utf-8");
206
+ result.set("X-Accel-Buffering", "no");
207
+ return result;
208
+ }
209
+ function sseResponse(execute, headers) {
210
+ const stream = new TransformStream();
211
+ const writer = stream.writable.getWriter();
212
+ const encoder = new TextEncoder();
213
+ const write = (value) => writer.write(encoder.encode(`data: ${JSON.stringify(value)}
214
+
215
+ `));
216
+ void execute(write).catch(async (error) => {
217
+ const message = "The Agent stream failed.";
218
+ const event = {
219
+ version: "run-stream.v3",
220
+ seq: Number.MAX_SAFE_INTEGER,
221
+ ts: (/* @__PURE__ */ new Date()).toISOString(),
222
+ event: "run.failed",
223
+ run_id: "bff-stream-error",
224
+ stage: "run",
225
+ payload: { error: message }
226
+ };
227
+ await write(event).catch(() => void 0);
228
+ }).finally(() => writer.close().catch(() => void 0));
229
+ return new Response(stream.readable, { headers: sseHeaders(headers) });
230
+ }
231
+ function bootstrapDocument(raw, agentVersionId) {
232
+ const features = objectValue(raw.features);
233
+ const selectable = objectValue(features.models).selectable === true;
234
+ const resolvedVersionId = stringValue(raw.agent_version_id) || agentVersionId || "";
235
+ const versionNumber = Number(raw.agent_version_number || 0);
236
+ return {
237
+ version: String(raw.version || "agents24.embed.bootstrap.v1"),
238
+ deployment: {
239
+ id: "server-embed",
240
+ name: String(raw.agent_name || "Agents24 Agent"),
241
+ version_policy: agentVersionId ? { mode: "pinned", version_id: agentVersionId } : { mode: "latest_published" },
242
+ resolved_agent_version_id: resolvedVersionId,
243
+ resolved_agent_version_number: Number.isFinite(versionNumber) ? versionNumber : 0,
244
+ policy_epoch: 0
245
+ },
246
+ authentication: { modes: ["server_embed"], access_token_lifetime_seconds: 0, jwks_path: "" },
247
+ capabilities: ["attachments.read", "attachments.write", "feedback.write", ...selectable ? ["models.select"] : []],
248
+ features,
249
+ native_profiles: []
250
+ };
251
+ }
252
+ function createAgents24BffHandler(options) {
253
+ const agentId = normalized(options.agentId, "agentId");
254
+ const agentVersionId = stringValue(options.agentVersionId);
255
+ const deploymentId = normalized(options.deploymentId, "deploymentId");
256
+ const basePath = normalizeBasePath(options.basePath);
257
+ const embed = options.client.embed;
258
+ return async (request) => {
259
+ const url = new URL(request.url);
260
+ if (url.pathname !== basePath && !url.pathname.startsWith(`${basePath}/`)) {
261
+ return publicError(404, "NOT_FOUND", "The requested BFF route does not exist.");
262
+ }
263
+ if (!browserRequestIsSameOrigin(request)) {
264
+ return publicError(403, "BFF_ORIGIN_DENIED", "The BFF accepts same-origin browser requests only.");
265
+ }
266
+ const path = url.pathname.slice(basePath.length) || "/";
267
+ let resolution;
268
+ try {
269
+ resolution = await options.resolvePrincipal(request);
270
+ resolution.principal.issuer = normalized(resolution.principal.issuer, "issuer");
271
+ resolution.principal.subject = normalized(resolution.principal.subject, "subject");
272
+ } catch {
273
+ return publicError(401, "PRINCIPAL_REQUIRED", "A valid application session is required.");
274
+ }
275
+ const principal = resolution.principal;
276
+ const idempotencyKey = stringValue(request.headers.get("idempotency-key"));
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
+ };
298
+ let response;
299
+ if (request.method === "GET" && path === "/bootstrap") {
300
+ const raw = await embed.getRuntimeBootstrap(agentId, agentVersionId);
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
+ }));
308
+ } else if (request.method === "POST" && path === "/chat/stream") {
309
+ const body = await requestJson(request);
310
+ const input = stringValue(body.input);
311
+ const attachmentIds = stringList(body.attachmentIds);
312
+ if (!input && attachmentIds.length === 0) throw new BffInputError(400, "INPUT_REQUIRED", "A message or attachment is required.");
313
+ response = sseResponse(async (write) => {
314
+ await embed.streamAgent(agentId, {
315
+ ...identity,
316
+ ...agentVersionId ? { agent_version_id: agentVersionId } : {},
317
+ ...input ? { input } : {},
318
+ attachment_ids: attachmentIds,
319
+ ...stringValue(body.threadId) ? { thread_id: stringValue(body.threadId) } : {},
320
+ ...stringValue(body.requestedModelId) ? { requested_model_id: stringValue(body.requestedModelId) } : {},
321
+ tool_inputs: objectValue(body.toolInputs),
322
+ metadata: objectValue(body.metadata),
323
+ client: objectValue(body.client)
324
+ }, (event) => write(event), { idempotencyKey, signal: request.signal });
325
+ });
326
+ } else if (request.method === "POST" && /^\/runs\/[^/]+\/attach$/.test(path)) {
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
+ }
333
+ response = sseResponse(async (write) => {
334
+ await embed.attachAgentRun(agentId, runId, {
335
+ ...identity,
336
+ ...cursor === void 0 ? {} : { cursor: Number(cursor) }
337
+ }, (event) => write(event), {
338
+ idempotencyKey,
339
+ signal: request.signal
340
+ });
341
+ });
342
+ } else if (request.method === "POST" && /^\/runs\/[^/]+\/cancel$/.test(path)) {
343
+ const runId = routeId(path, /^\/runs\/([^/]+)\/cancel$/, "runId");
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
+ }));
367
+ } else if (request.method === "GET" && path === "/threads") {
368
+ const skip = Number(url.searchParams.get("skip") || 0);
369
+ const limit = Number(url.searchParams.get("limit") || 20);
370
+ response = json(await embed.listAgentThreads(agentId, {
371
+ ...threadIdentity,
372
+ skip: Number.isFinite(skip) ? skip : 0,
373
+ limit: Number.isFinite(limit) ? limit : 20
374
+ }));
375
+ } else if (request.method === "GET" && path === "/threads/events") {
376
+ const rawCursor = url.searchParams.get("cursor");
377
+ const cursor = rawCursor === null ? void 0 : Number(rawCursor);
378
+ response = sseResponse(async (write) => {
379
+ await embed.subscribeAgentThreadEvents(agentId, {
380
+ ...threadIdentity,
381
+ ...cursor !== void 0 && Number.isFinite(cursor) ? { cursor } : {},
382
+ signal: request.signal
383
+ }, (event) => write(event));
384
+ });
385
+ } else if (/^\/threads\/[^/]+$/.test(path) && request.method === "GET") {
386
+ const threadId = routeId(path, /^\/threads\/([^/]+)$/, "threadId");
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);
391
+ response = json(await embed.getAgentThread(agentId, threadId, {
392
+ ...threadIdentity,
393
+ ...before !== void 0 && Number.isFinite(before) ? { beforeTurnIndex: before } : {},
394
+ ...limit !== void 0 && Number.isFinite(limit) ? { limit } : {}
395
+ }));
396
+ } else if (/^\/threads\/[^/]+$/.test(path) && request.method === "DELETE") {
397
+ const threadId = routeId(path, /^\/threads\/([^/]+)$/, "threadId");
398
+ response = json(await embed.deleteAgentThread(agentId, threadId, threadIdentity, { idempotencyKey }));
399
+ } else if (request.method === "POST" && path === "/attachments/upload") {
400
+ const form = await request.formData();
401
+ const files = form.getAll("files").filter((entry) => entry instanceof File);
402
+ if (files.length === 0) throw new BffInputError(400, "ATTACHMENT_REQUIRED", "At least one attachment is required.");
403
+ response = json(await embed.uploadAgentAttachments(agentId, {
404
+ ...threadIdentity,
405
+ files,
406
+ ...stringValue(form.get("thread_id")) ? { threadId: stringValue(form.get("thread_id")) } : {},
407
+ idempotencyKey
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
+ }));
421
+ } else if (request.method === "POST" && /^\/runs\/[^/]+\/hitl\/[^/]+\/resume$/.test(path)) {
422
+ const match = /^\/runs\/([^/]+)\/hitl\/([^/]+)\/resume$/.exec(path);
423
+ const runId = routeId(path, /^\/runs\/([^/]+)\/hitl\//, "runId");
424
+ const interruptId = match?.[2] ? decodeURIComponent(match[2]) : "";
425
+ const body = await requestJson(request);
426
+ response = json(await embed.resumeAgentRun(agentId, runId, {
427
+ ...identity,
428
+ schema_version: "agents24.hitl.resume.v2",
429
+ interrupt_id: normalized(interruptId, "interruptId"),
430
+ action: normalized(String(body.action || ""), "action"),
431
+ ...stringValue(body.comment) ? { comment: stringValue(body.comment) } : {},
432
+ ...Array.isArray(body.answers) ? { answers: body.answers } : {}
433
+ }, { idempotencyKey }));
434
+ } else if (request.method === "POST" && /^\/runs\/[^/]+\/mcp\/servers\/[^/]+\/auth\/start$/.test(path)) {
435
+ const match = /^\/runs\/([^/]+)\/mcp\/servers\/([^/]+)\/auth\/start$/.exec(path);
436
+ const runId = match?.[1] ? decodeURIComponent(match[1]) : "";
437
+ const serverId = match?.[2] ? decodeURIComponent(match[2]) : "";
438
+ const body = await requestJson(request);
439
+ const callbackOrigin = new URL(request.url).origin;
440
+ response = json(await embed.startAgentRunMcpAuth(agentId, normalized(runId, "runId"), normalized(serverId, "serverId"), {
441
+ ...identity,
442
+ interrupt_id: normalized(String(body.interruptId || ""), "interruptId"),
443
+ callback_origin: callbackOrigin,
444
+ popup_nonce: normalized(String(body.popupNonce || ""), "popupNonce")
445
+ }, { idempotencyKey }));
446
+ } else if (request.method === "POST" && path === "/mcp/callback/redeem") {
447
+ throw new BffInputError(409, "MCP_CALLBACK_DIRECT", "Server-embed MCP callbacks complete in the authorization popup.");
448
+ } else if (request.method === "POST" && path === "/session/revoke") {
449
+ const headers = await options.revokePrincipal?.(request, principal);
450
+ response = new Response(null, { status: 204, ...headers ? { headers } : {} });
451
+ } else {
452
+ response = publicError(404, "NOT_FOUND", "The requested BFF route does not exist.");
453
+ }
454
+ return mergeHeaders(response, resolution.responseHeaders);
455
+ } catch (error) {
456
+ if (error instanceof BffInputError) {
457
+ return mergeHeaders(publicError(error.status, error.code, error.message), resolution.responseHeaders);
458
+ }
459
+ return mergeHeaders(errorResponse(error), resolution.responseHeaders);
460
+ }
461
+ };
462
+ }
463
+
464
+ exports.createAgents24BffHandler = createAgents24BffHandler;
465
+ //# sourceMappingURL=bff.cjs.map
466
+ //# sourceMappingURL=bff.cjs.map