@axiom-lattice/gateway 4.1.6 → 4.1.7
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.
- package/dist/{a2a-standard-G3J5ZHQ7.mjs → a2a-standard-WC2BSOFV.mjs} +2 -2
- package/dist/a2a-standard-WC2BSOFV.mjs.map +1 -0
- package/dist/index.js +861 -377
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +851 -370
- package/dist/index.mjs.map +1 -1
- package/package.json +6 -6
- package/dist/a2a-standard-G3J5ZHQ7.mjs.map +0 -1
package/dist/index.mjs
CHANGED
|
@@ -6424,14 +6424,202 @@ function registerWorkspaceRoutes(app2, projectCollaboration) {
|
|
|
6424
6424
|
"/api/filesystem/browse",
|
|
6425
6425
|
controller.browseFilesystem.bind(controller)
|
|
6426
6426
|
);
|
|
6427
|
+
return controller;
|
|
6427
6428
|
}
|
|
6428
6429
|
|
|
6430
|
+
// src/services/collaboration/ProjectAccessService.ts
|
|
6431
|
+
import { randomUUID as randomUUID6 } from "crypto";
|
|
6432
|
+
var ProjectCollaborationError = class extends Error {
|
|
6433
|
+
/** Creates a project collaboration error. */
|
|
6434
|
+
constructor(code, statusCode, message) {
|
|
6435
|
+
super(message);
|
|
6436
|
+
this.code = code;
|
|
6437
|
+
this.statusCode = statusCode;
|
|
6438
|
+
this.name = "ProjectCollaborationError";
|
|
6439
|
+
}
|
|
6440
|
+
};
|
|
6441
|
+
var allowedRoles = {
|
|
6442
|
+
read: /* @__PURE__ */ new Set(["owner", "admin", "member", "viewer"]),
|
|
6443
|
+
send: /* @__PURE__ */ new Set(["owner", "admin", "member"]),
|
|
6444
|
+
manage: /* @__PURE__ */ new Set(["owner", "admin"])
|
|
6445
|
+
};
|
|
6446
|
+
var membershipConflictNames = /* @__PURE__ */ new Set([
|
|
6447
|
+
"DuplicateProjectMembershipError",
|
|
6448
|
+
"ProjectMembershipIdConflictError"
|
|
6449
|
+
]);
|
|
6450
|
+
function isProjectMembershipConflict(error) {
|
|
6451
|
+
return error instanceof Error && membershipConflictNames.has(error.name);
|
|
6452
|
+
}
|
|
6453
|
+
var ProjectAccessService = class {
|
|
6454
|
+
/** Creates a project access service from explicitly injected stores. */
|
|
6455
|
+
constructor(deps) {
|
|
6456
|
+
this.deps = deps;
|
|
6457
|
+
this.idFactory = deps.idFactory ?? randomUUID6;
|
|
6458
|
+
}
|
|
6459
|
+
/** Returns the project and active membership when the user has the requested capability. */
|
|
6460
|
+
async requireProjectAccess(input) {
|
|
6461
|
+
const project = await this.deps.projectStore.getProjectById(input.tenantId, input.projectId);
|
|
6462
|
+
if (!project) {
|
|
6463
|
+
throw new ProjectCollaborationError("PROJECT_NOT_FOUND", 404, "Project not found");
|
|
6464
|
+
}
|
|
6465
|
+
if (project.kind === "public") {
|
|
6466
|
+
return this.requirePublicProjectAccess(project, input);
|
|
6467
|
+
}
|
|
6468
|
+
const membership = await this.deps.membershipStore.findByUser(
|
|
6469
|
+
input.tenantId,
|
|
6470
|
+
input.projectId,
|
|
6471
|
+
input.userId
|
|
6472
|
+
);
|
|
6473
|
+
if (membership?.status === "active") {
|
|
6474
|
+
this.requireCapability(membership, input.capability);
|
|
6475
|
+
return { project, membership };
|
|
6476
|
+
}
|
|
6477
|
+
const memberships = await this.deps.membershipStore.list(input.tenantId, input.projectId);
|
|
6478
|
+
if (memberships.length !== 0) this.accessDenied();
|
|
6479
|
+
if (project.kind === "personal") this.accessDenied();
|
|
6480
|
+
const tenantLink = await this.deps.userTenantLinkStore.getLink(input.userId, input.tenantId);
|
|
6481
|
+
if (tenantLink?.role !== "owner" && tenantLink?.role !== "admin") this.accessDenied();
|
|
6482
|
+
let claim;
|
|
6483
|
+
try {
|
|
6484
|
+
claim = await this.deps.membershipStore.createInitialOwner({
|
|
6485
|
+
id: this.idFactory(),
|
|
6486
|
+
tenantId: input.tenantId,
|
|
6487
|
+
projectId: input.projectId,
|
|
6488
|
+
userId: input.userId
|
|
6489
|
+
});
|
|
6490
|
+
} catch (error) {
|
|
6491
|
+
if (isProjectMembershipConflict(error)) {
|
|
6492
|
+
throw new ProjectCollaborationError(
|
|
6493
|
+
"PROJECT_MEMBERSHIP_CONFLICT",
|
|
6494
|
+
409,
|
|
6495
|
+
"Project membership could not be initialized"
|
|
6496
|
+
);
|
|
6497
|
+
}
|
|
6498
|
+
throw error;
|
|
6499
|
+
}
|
|
6500
|
+
if (claim.kind === "created" || claim.kind === "existing") {
|
|
6501
|
+
return { project, membership: claim.membership };
|
|
6502
|
+
}
|
|
6503
|
+
const racedMembership = await this.deps.membershipStore.findByUser(
|
|
6504
|
+
input.tenantId,
|
|
6505
|
+
input.projectId,
|
|
6506
|
+
input.userId
|
|
6507
|
+
);
|
|
6508
|
+
if (racedMembership?.status !== "active") this.accessDenied();
|
|
6509
|
+
this.requireCapability(racedMembership, input.capability);
|
|
6510
|
+
return { project, membership: racedMembership };
|
|
6511
|
+
}
|
|
6512
|
+
/** Authorizes an already-existing membership, or any tenant member for public rooms; never mutates persistence. */
|
|
6513
|
+
async requireExistingProjectAccess(input) {
|
|
6514
|
+
const project = await this.deps.projectStore.getProjectById(input.tenantId, input.projectId);
|
|
6515
|
+
if (!project) throw new ProjectCollaborationError("PROJECT_NOT_FOUND", 404, "Project not found");
|
|
6516
|
+
if (project.kind === "public") {
|
|
6517
|
+
return this.requireExistingPublicProjectAccess(project, input);
|
|
6518
|
+
}
|
|
6519
|
+
const membership = await this.deps.membershipStore.findByUser(
|
|
6520
|
+
input.tenantId,
|
|
6521
|
+
input.projectId,
|
|
6522
|
+
input.userId
|
|
6523
|
+
);
|
|
6524
|
+
if (!membership || membership.tenantId !== input.tenantId || membership.projectId !== input.projectId || membership.userId !== input.userId || membership.status !== "active") this.accessDenied();
|
|
6525
|
+
this.requireCapability(membership, input.capability);
|
|
6526
|
+
return { project, membership };
|
|
6527
|
+
}
|
|
6528
|
+
/** Authorizes roster visibility: read for public rooms, manage elsewhere. */
|
|
6529
|
+
async requireRosterAccess(input) {
|
|
6530
|
+
const project = await this.deps.projectStore.getProjectById(input.tenantId, input.projectId);
|
|
6531
|
+
if (!project) throw new ProjectCollaborationError("PROJECT_NOT_FOUND", 404, "Project not found");
|
|
6532
|
+
return this.requireProjectAccess({
|
|
6533
|
+
...input,
|
|
6534
|
+
capability: project.kind === "public" ? "read" : "manage"
|
|
6535
|
+
});
|
|
6536
|
+
}
|
|
6537
|
+
async requirePublicProjectAccess(project, input) {
|
|
6538
|
+
const membership = await this.deps.membershipStore.findByUser(
|
|
6539
|
+
input.tenantId,
|
|
6540
|
+
input.projectId,
|
|
6541
|
+
input.userId
|
|
6542
|
+
);
|
|
6543
|
+
if (membership?.status === "active") {
|
|
6544
|
+
this.requireCapability(membership, input.capability);
|
|
6545
|
+
return { project, membership };
|
|
6546
|
+
}
|
|
6547
|
+
const materialized = await this.materializePublicMembership(input);
|
|
6548
|
+
this.requireCapability(materialized, input.capability);
|
|
6549
|
+
return { project, membership: materialized };
|
|
6550
|
+
}
|
|
6551
|
+
async requireExistingPublicProjectAccess(project, input) {
|
|
6552
|
+
const membership = await this.deps.membershipStore.findByUser(
|
|
6553
|
+
input.tenantId,
|
|
6554
|
+
input.projectId,
|
|
6555
|
+
input.userId
|
|
6556
|
+
);
|
|
6557
|
+
if (membership?.status === "active") {
|
|
6558
|
+
this.requireCapability(membership, input.capability);
|
|
6559
|
+
return { project, membership };
|
|
6560
|
+
}
|
|
6561
|
+
if (membership) this.accessDenied();
|
|
6562
|
+
const tenantLink = await this.deps.userTenantLinkStore.getLink(input.userId, input.tenantId);
|
|
6563
|
+
if (!tenantLink) this.accessDenied();
|
|
6564
|
+
const role = tenantLink.role === "owner" || tenantLink.role === "admin" ? "admin" : "member";
|
|
6565
|
+
const synthetic = {
|
|
6566
|
+
id: `public:${input.userId}`,
|
|
6567
|
+
tenantId: input.tenantId,
|
|
6568
|
+
projectId: input.projectId,
|
|
6569
|
+
userId: input.userId,
|
|
6570
|
+
role,
|
|
6571
|
+
status: "active",
|
|
6572
|
+
joinedAt: project.createdAt,
|
|
6573
|
+
updatedAt: project.updatedAt
|
|
6574
|
+
};
|
|
6575
|
+
this.requireCapability(synthetic, input.capability);
|
|
6576
|
+
return { project, membership: synthetic };
|
|
6577
|
+
}
|
|
6578
|
+
async materializePublicMembership(input) {
|
|
6579
|
+
const tenantLink = await this.deps.userTenantLinkStore.getLink(input.userId, input.tenantId);
|
|
6580
|
+
if (!tenantLink) this.accessDenied();
|
|
6581
|
+
const existing = await this.deps.membershipStore.findByUser(
|
|
6582
|
+
input.tenantId,
|
|
6583
|
+
input.projectId,
|
|
6584
|
+
input.userId
|
|
6585
|
+
);
|
|
6586
|
+
if (existing) this.accessDenied();
|
|
6587
|
+
const role = tenantLink.role === "owner" || tenantLink.role === "admin" ? "admin" : "member";
|
|
6588
|
+
try {
|
|
6589
|
+
return await this.deps.membershipStore.create({
|
|
6590
|
+
id: this.idFactory(),
|
|
6591
|
+
tenantId: input.tenantId,
|
|
6592
|
+
projectId: input.projectId,
|
|
6593
|
+
userId: input.userId,
|
|
6594
|
+
role,
|
|
6595
|
+
status: "active"
|
|
6596
|
+
});
|
|
6597
|
+
} catch (error) {
|
|
6598
|
+
if (isProjectMembershipConflict(error)) {
|
|
6599
|
+
const raced = await this.deps.membershipStore.findByUser(
|
|
6600
|
+
input.tenantId,
|
|
6601
|
+
input.projectId,
|
|
6602
|
+
input.userId
|
|
6603
|
+
);
|
|
6604
|
+
if (raced?.status === "active") return raced;
|
|
6605
|
+
}
|
|
6606
|
+
throw error;
|
|
6607
|
+
}
|
|
6608
|
+
}
|
|
6609
|
+
requireCapability(membership, capability2) {
|
|
6610
|
+
if (!allowedRoles[capability2].has(membership.role)) this.accessDenied();
|
|
6611
|
+
}
|
|
6612
|
+
accessDenied() {
|
|
6613
|
+
throw new ProjectCollaborationError("PROJECT_ACCESS_DENIED", 403, "Project access denied");
|
|
6614
|
+
}
|
|
6615
|
+
};
|
|
6616
|
+
|
|
6429
6617
|
// src/controllers/database-configs.ts
|
|
6430
6618
|
import {
|
|
6431
6619
|
getStoreLattice as getStoreLattice7,
|
|
6432
6620
|
sqlDatabaseManager
|
|
6433
6621
|
} from "@axiom-lattice/core";
|
|
6434
|
-
import { randomUUID as
|
|
6622
|
+
import { randomUUID as randomUUID7 } from "crypto";
|
|
6435
6623
|
function getTenantId9(request) {
|
|
6436
6624
|
const userTenantId = request.user?.tenantId;
|
|
6437
6625
|
if (userTenantId) {
|
|
@@ -6509,7 +6697,7 @@ async function createDatabaseConfig(request, reply) {
|
|
|
6509
6697
|
message: "Database configuration with this key already exists"
|
|
6510
6698
|
};
|
|
6511
6699
|
}
|
|
6512
|
-
const id3 = body.id ||
|
|
6700
|
+
const id3 = body.id || randomUUID7();
|
|
6513
6701
|
const config = await store.createConfig(tenantId, id3, body);
|
|
6514
6702
|
try {
|
|
6515
6703
|
sqlDatabaseManager.registerDatabase(tenantId, config.key, config.config);
|
|
@@ -6714,7 +6902,7 @@ import {
|
|
|
6714
6902
|
metricsServerManager as metricsServerManager2,
|
|
6715
6903
|
SemanticMetricsClient as SemanticMetricsClient2
|
|
6716
6904
|
} from "@axiom-lattice/core";
|
|
6717
|
-
import { randomUUID as
|
|
6905
|
+
import { randomUUID as randomUUID8 } from "crypto";
|
|
6718
6906
|
function getTenantId10(request) {
|
|
6719
6907
|
const userTenantId = request.user?.tenantId;
|
|
6720
6908
|
if (userTenantId) {
|
|
@@ -6795,7 +6983,7 @@ async function createMetricsServerConfig(request, reply) {
|
|
|
6795
6983
|
message: "selectedDataSources is required for semantic metrics servers"
|
|
6796
6984
|
};
|
|
6797
6985
|
}
|
|
6798
|
-
const id3 = body.id ||
|
|
6986
|
+
const id3 = body.id || randomUUID8();
|
|
6799
6987
|
const configData = {
|
|
6800
6988
|
key: body.key,
|
|
6801
6989
|
name: body.name,
|
|
@@ -9302,7 +9490,7 @@ function registerChannelRoutes(app2, dependencies) {
|
|
|
9302
9490
|
}
|
|
9303
9491
|
|
|
9304
9492
|
// src/controllers/channel-installations.ts
|
|
9305
|
-
import { randomUUID as
|
|
9493
|
+
import { randomUUID as randomUUID9 } from "crypto";
|
|
9306
9494
|
import {
|
|
9307
9495
|
descriptorDataValue
|
|
9308
9496
|
} from "@axiom-lattice/protocols";
|
|
@@ -9506,7 +9694,7 @@ async function createChannelInstallation(request, reply) {
|
|
|
9506
9694
|
};
|
|
9507
9695
|
}
|
|
9508
9696
|
const store = await getInstallationStore();
|
|
9509
|
-
const installationId =
|
|
9697
|
+
const installationId = randomUUID9();
|
|
9510
9698
|
const createInput = {
|
|
9511
9699
|
channel: body.channel,
|
|
9512
9700
|
name: body.name,
|
|
@@ -10466,7 +10654,7 @@ async function getLocalA2ATemplates(_request, reply) {
|
|
|
10466
10654
|
}
|
|
10467
10655
|
|
|
10468
10656
|
// src/routes/a2a-test-connection.ts
|
|
10469
|
-
import { randomUUID as
|
|
10657
|
+
import { randomUUID as randomUUID10 } from "crypto";
|
|
10470
10658
|
function describeTestConnectionError(error, agentCardUrl) {
|
|
10471
10659
|
if (!(error instanceof Error)) {
|
|
10472
10660
|
return `Failed to fetch agent card from ${agentCardUrl}: ${String(error)}`;
|
|
@@ -10506,7 +10694,7 @@ async function ensureLocalRuntimeForConnectionTest(localRuntime, agentCardUrl) {
|
|
|
10506
10694
|
return { error: portValidation.error };
|
|
10507
10695
|
}
|
|
10508
10696
|
const state = await localA2AService.startProvider(
|
|
10509
|
-
`test-${
|
|
10697
|
+
`test-${randomUUID10()}`,
|
|
10510
10698
|
testRuntime
|
|
10511
10699
|
);
|
|
10512
10700
|
if (state.status === "running") return { runtimeId: state.runtimeId };
|
|
@@ -10523,264 +10711,77 @@ function registerA2ATestConnectionRoute(app2) {
|
|
|
10523
10711
|
const { agentCardUrl, apiKey, localRuntime } = request.body ?? {};
|
|
10524
10712
|
const normalizedAgentCardUrl = normalizeA2AAgentCardUrl(
|
|
10525
10713
|
agentCardUrl,
|
|
10526
|
-
localRuntime ? "http://" : "https://"
|
|
10527
|
-
);
|
|
10528
|
-
if (!normalizedAgentCardUrl) {
|
|
10529
|
-
return reply.status(400).send({ success: false, error: "agentCardUrl is required" });
|
|
10530
|
-
}
|
|
10531
|
-
let testRuntimeId = null;
|
|
10532
|
-
try {
|
|
10533
|
-
const headers = {};
|
|
10534
|
-
if (apiKey) headers["Authorization"] = `Bearer ${apiKey}`;
|
|
10535
|
-
if (localRuntime) {
|
|
10536
|
-
const localStartResult = await ensureLocalRuntimeForConnectionTest(localRuntime, normalizedAgentCardUrl);
|
|
10537
|
-
if ("error" in localStartResult) {
|
|
10538
|
-
return reply.send({ success: true, data: { reachable: false, error: localStartResult.error } });
|
|
10539
|
-
}
|
|
10540
|
-
testRuntimeId = localStartResult.runtimeId;
|
|
10541
|
-
}
|
|
10542
|
-
const resp = await fetch(normalizedAgentCardUrl, { headers, signal: AbortSignal.timeout(8e3) });
|
|
10543
|
-
if (!resp.ok) {
|
|
10544
|
-
const body = await readShortResponseBody(resp);
|
|
10545
|
-
const suffix = body ? `: ${body}` : "";
|
|
10546
|
-
return reply.send({ success: true, data: { reachable: false, error: `HTTP ${resp.status} ${resp.statusText}${suffix}` } });
|
|
10547
|
-
}
|
|
10548
|
-
const card = await resp.json();
|
|
10549
|
-
return reply.send({ success: true, data: { reachable: true, agentName: card.name, agentCard: card } });
|
|
10550
|
-
} catch (error) {
|
|
10551
|
-
return reply.send({
|
|
10552
|
-
success: true,
|
|
10553
|
-
data: {
|
|
10554
|
-
reachable: false,
|
|
10555
|
-
error: describeTestConnectionError(error, normalizedAgentCardUrl)
|
|
10556
|
-
}
|
|
10557
|
-
});
|
|
10558
|
-
} finally {
|
|
10559
|
-
if (testRuntimeId) {
|
|
10560
|
-
localA2AService.removeState(testRuntimeId);
|
|
10561
|
-
}
|
|
10562
|
-
}
|
|
10563
|
-
});
|
|
10564
|
-
}
|
|
10565
|
-
|
|
10566
|
-
// src/routes/task-routes.ts
|
|
10567
|
-
function registerTaskRoutes(app2, controller) {
|
|
10568
|
-
app2.get("/api/tasks", controller.list);
|
|
10569
|
-
app2.get("/api/tasks/:id", controller.get);
|
|
10570
|
-
app2.post("/api/tasks", controller.create);
|
|
10571
|
-
app2.put("/api/tasks/:id", controller.update);
|
|
10572
|
-
app2.patch("/api/tasks/:id", controller.update);
|
|
10573
|
-
app2.delete("/api/tasks/:id", controller.remove);
|
|
10574
|
-
app2.patch("/api/tasks/:id/complete", controller.complete);
|
|
10575
|
-
app2.get("/api/tasks/:id/work-items", controller.workItems);
|
|
10576
|
-
app2.post("/api/projects/:projectId/tasks/:id/reassign", controller.reassign);
|
|
10577
|
-
}
|
|
10578
|
-
|
|
10579
|
-
// src/routes/index.ts
|
|
10580
|
-
import {
|
|
10581
|
-
agentInstanceManager as agentInstanceManager6,
|
|
10582
|
-
agentLatticeManager as agentLatticeManager4,
|
|
10583
|
-
createProjectTaskReassignmentAuthorizer,
|
|
10584
|
-
createTaskLifecycleService,
|
|
10585
|
-
createTrustedAgentMessageDispatcher,
|
|
10586
|
-
getStoreLattice as getStoreLattice16,
|
|
10587
|
-
modelLatticeManager as modelLatticeManager3,
|
|
10588
|
-
ProjectTaskOwnerPolicy,
|
|
10589
|
-
projectMutationMutex as projectMutationMutex2,
|
|
10590
|
-
projectTaskAdmissionNotifier as projectTaskAdmissionNotifier2,
|
|
10591
|
-
taskLifecycleNotifier as taskLifecycleNotifier2
|
|
10592
|
-
} from "@axiom-lattice/core";
|
|
10593
|
-
|
|
10594
|
-
// src/controllers/tasks.ts
|
|
10595
|
-
import { snapshotExactRecord as snapshotExactRecord3 } from "@axiom-lattice/protocols";
|
|
10596
|
-
import { ProjectTaskOwnerPolicyError, projectTaskAdmissionNotifier } from "@axiom-lattice/core";
|
|
10597
|
-
|
|
10598
|
-
// src/services/collaboration/ProjectAccessService.ts
|
|
10599
|
-
import { randomUUID as randomUUID10 } from "crypto";
|
|
10600
|
-
var ProjectCollaborationError = class extends Error {
|
|
10601
|
-
/** Creates a project collaboration error. */
|
|
10602
|
-
constructor(code, statusCode, message) {
|
|
10603
|
-
super(message);
|
|
10604
|
-
this.code = code;
|
|
10605
|
-
this.statusCode = statusCode;
|
|
10606
|
-
this.name = "ProjectCollaborationError";
|
|
10607
|
-
}
|
|
10608
|
-
};
|
|
10609
|
-
var allowedRoles = {
|
|
10610
|
-
read: /* @__PURE__ */ new Set(["owner", "admin", "member", "viewer"]),
|
|
10611
|
-
send: /* @__PURE__ */ new Set(["owner", "admin", "member"]),
|
|
10612
|
-
manage: /* @__PURE__ */ new Set(["owner", "admin"])
|
|
10613
|
-
};
|
|
10614
|
-
var membershipConflictNames = /* @__PURE__ */ new Set([
|
|
10615
|
-
"DuplicateProjectMembershipError",
|
|
10616
|
-
"ProjectMembershipIdConflictError"
|
|
10617
|
-
]);
|
|
10618
|
-
function isProjectMembershipConflict(error) {
|
|
10619
|
-
return error instanceof Error && membershipConflictNames.has(error.name);
|
|
10620
|
-
}
|
|
10621
|
-
var ProjectAccessService = class {
|
|
10622
|
-
/** Creates a project access service from explicitly injected stores. */
|
|
10623
|
-
constructor(deps) {
|
|
10624
|
-
this.deps = deps;
|
|
10625
|
-
this.idFactory = deps.idFactory ?? randomUUID10;
|
|
10626
|
-
}
|
|
10627
|
-
/** Returns the project and active membership when the user has the requested capability. */
|
|
10628
|
-
async requireProjectAccess(input) {
|
|
10629
|
-
const project = await this.deps.projectStore.getProjectById(input.tenantId, input.projectId);
|
|
10630
|
-
if (!project) {
|
|
10631
|
-
throw new ProjectCollaborationError("PROJECT_NOT_FOUND", 404, "Project not found");
|
|
10632
|
-
}
|
|
10633
|
-
if (project.kind === "public") {
|
|
10634
|
-
return this.requirePublicProjectAccess(project, input);
|
|
10635
|
-
}
|
|
10636
|
-
const membership = await this.deps.membershipStore.findByUser(
|
|
10637
|
-
input.tenantId,
|
|
10638
|
-
input.projectId,
|
|
10639
|
-
input.userId
|
|
10640
|
-
);
|
|
10641
|
-
if (membership?.status === "active") {
|
|
10642
|
-
this.requireCapability(membership, input.capability);
|
|
10643
|
-
return { project, membership };
|
|
10644
|
-
}
|
|
10645
|
-
const memberships = await this.deps.membershipStore.list(input.tenantId, input.projectId);
|
|
10646
|
-
if (memberships.length !== 0) this.accessDenied();
|
|
10647
|
-
if (project.kind === "personal") this.accessDenied();
|
|
10648
|
-
const tenantLink = await this.deps.userTenantLinkStore.getLink(input.userId, input.tenantId);
|
|
10649
|
-
if (tenantLink?.role !== "owner" && tenantLink?.role !== "admin") this.accessDenied();
|
|
10650
|
-
let claim;
|
|
10651
|
-
try {
|
|
10652
|
-
claim = await this.deps.membershipStore.createInitialOwner({
|
|
10653
|
-
id: this.idFactory(),
|
|
10654
|
-
tenantId: input.tenantId,
|
|
10655
|
-
projectId: input.projectId,
|
|
10656
|
-
userId: input.userId
|
|
10657
|
-
});
|
|
10658
|
-
} catch (error) {
|
|
10659
|
-
if (isProjectMembershipConflict(error)) {
|
|
10660
|
-
throw new ProjectCollaborationError(
|
|
10661
|
-
"PROJECT_MEMBERSHIP_CONFLICT",
|
|
10662
|
-
409,
|
|
10663
|
-
"Project membership could not be initialized"
|
|
10664
|
-
);
|
|
10665
|
-
}
|
|
10666
|
-
throw error;
|
|
10667
|
-
}
|
|
10668
|
-
if (claim.kind === "created" || claim.kind === "existing") {
|
|
10669
|
-
return { project, membership: claim.membership };
|
|
10670
|
-
}
|
|
10671
|
-
const racedMembership = await this.deps.membershipStore.findByUser(
|
|
10672
|
-
input.tenantId,
|
|
10673
|
-
input.projectId,
|
|
10674
|
-
input.userId
|
|
10675
|
-
);
|
|
10676
|
-
if (racedMembership?.status !== "active") this.accessDenied();
|
|
10677
|
-
this.requireCapability(racedMembership, input.capability);
|
|
10678
|
-
return { project, membership: racedMembership };
|
|
10679
|
-
}
|
|
10680
|
-
/** Authorizes an already-existing membership, or any tenant member for public rooms; never mutates persistence. */
|
|
10681
|
-
async requireExistingProjectAccess(input) {
|
|
10682
|
-
const project = await this.deps.projectStore.getProjectById(input.tenantId, input.projectId);
|
|
10683
|
-
if (!project) throw new ProjectCollaborationError("PROJECT_NOT_FOUND", 404, "Project not found");
|
|
10684
|
-
if (project.kind === "public") {
|
|
10685
|
-
return this.requireExistingPublicProjectAccess(project, input);
|
|
10686
|
-
}
|
|
10687
|
-
const membership = await this.deps.membershipStore.findByUser(
|
|
10688
|
-
input.tenantId,
|
|
10689
|
-
input.projectId,
|
|
10690
|
-
input.userId
|
|
10691
|
-
);
|
|
10692
|
-
if (!membership || membership.tenantId !== input.tenantId || membership.projectId !== input.projectId || membership.userId !== input.userId || membership.status !== "active") this.accessDenied();
|
|
10693
|
-
this.requireCapability(membership, input.capability);
|
|
10694
|
-
return { project, membership };
|
|
10695
|
-
}
|
|
10696
|
-
/** Authorizes roster visibility: read for public rooms, manage elsewhere. */
|
|
10697
|
-
async requireRosterAccess(input) {
|
|
10698
|
-
const project = await this.deps.projectStore.getProjectById(input.tenantId, input.projectId);
|
|
10699
|
-
if (!project) throw new ProjectCollaborationError("PROJECT_NOT_FOUND", 404, "Project not found");
|
|
10700
|
-
return this.requireProjectAccess({
|
|
10701
|
-
...input,
|
|
10702
|
-
capability: project.kind === "public" ? "read" : "manage"
|
|
10703
|
-
});
|
|
10704
|
-
}
|
|
10705
|
-
async requirePublicProjectAccess(project, input) {
|
|
10706
|
-
const membership = await this.deps.membershipStore.findByUser(
|
|
10707
|
-
input.tenantId,
|
|
10708
|
-
input.projectId,
|
|
10709
|
-
input.userId
|
|
10710
|
-
);
|
|
10711
|
-
if (membership?.status === "active") {
|
|
10712
|
-
this.requireCapability(membership, input.capability);
|
|
10713
|
-
return { project, membership };
|
|
10714
|
-
}
|
|
10715
|
-
const materialized = await this.materializePublicMembership(input);
|
|
10716
|
-
this.requireCapability(materialized, input.capability);
|
|
10717
|
-
return { project, membership: materialized };
|
|
10718
|
-
}
|
|
10719
|
-
async requireExistingPublicProjectAccess(project, input) {
|
|
10720
|
-
const membership = await this.deps.membershipStore.findByUser(
|
|
10721
|
-
input.tenantId,
|
|
10722
|
-
input.projectId,
|
|
10723
|
-
input.userId
|
|
10724
|
-
);
|
|
10725
|
-
if (membership?.status === "active") {
|
|
10726
|
-
this.requireCapability(membership, input.capability);
|
|
10727
|
-
return { project, membership };
|
|
10728
|
-
}
|
|
10729
|
-
if (membership) this.accessDenied();
|
|
10730
|
-
const tenantLink = await this.deps.userTenantLinkStore.getLink(input.userId, input.tenantId);
|
|
10731
|
-
if (!tenantLink) this.accessDenied();
|
|
10732
|
-
const role = tenantLink.role === "owner" || tenantLink.role === "admin" ? "admin" : "member";
|
|
10733
|
-
const synthetic = {
|
|
10734
|
-
id: `public:${input.userId}`,
|
|
10735
|
-
tenantId: input.tenantId,
|
|
10736
|
-
projectId: input.projectId,
|
|
10737
|
-
userId: input.userId,
|
|
10738
|
-
role,
|
|
10739
|
-
status: "active",
|
|
10740
|
-
joinedAt: project.createdAt,
|
|
10741
|
-
updatedAt: project.updatedAt
|
|
10742
|
-
};
|
|
10743
|
-
this.requireCapability(synthetic, input.capability);
|
|
10744
|
-
return { project, membership: synthetic };
|
|
10745
|
-
}
|
|
10746
|
-
async materializePublicMembership(input) {
|
|
10747
|
-
const tenantLink = await this.deps.userTenantLinkStore.getLink(input.userId, input.tenantId);
|
|
10748
|
-
if (!tenantLink) this.accessDenied();
|
|
10749
|
-
const existing = await this.deps.membershipStore.findByUser(
|
|
10750
|
-
input.tenantId,
|
|
10751
|
-
input.projectId,
|
|
10752
|
-
input.userId
|
|
10753
|
-
);
|
|
10754
|
-
if (existing) this.accessDenied();
|
|
10755
|
-
const role = tenantLink.role === "owner" || tenantLink.role === "admin" ? "admin" : "member";
|
|
10756
|
-
try {
|
|
10757
|
-
return await this.deps.membershipStore.create({
|
|
10758
|
-
id: this.idFactory(),
|
|
10759
|
-
tenantId: input.tenantId,
|
|
10760
|
-
projectId: input.projectId,
|
|
10761
|
-
userId: input.userId,
|
|
10762
|
-
role,
|
|
10763
|
-
status: "active"
|
|
10764
|
-
});
|
|
10714
|
+
localRuntime ? "http://" : "https://"
|
|
10715
|
+
);
|
|
10716
|
+
if (!normalizedAgentCardUrl) {
|
|
10717
|
+
return reply.status(400).send({ success: false, error: "agentCardUrl is required" });
|
|
10718
|
+
}
|
|
10719
|
+
let testRuntimeId = null;
|
|
10720
|
+
try {
|
|
10721
|
+
const headers = {};
|
|
10722
|
+
if (apiKey) headers["Authorization"] = `Bearer ${apiKey}`;
|
|
10723
|
+
if (localRuntime) {
|
|
10724
|
+
const localStartResult = await ensureLocalRuntimeForConnectionTest(localRuntime, normalizedAgentCardUrl);
|
|
10725
|
+
if ("error" in localStartResult) {
|
|
10726
|
+
return reply.send({ success: true, data: { reachable: false, error: localStartResult.error } });
|
|
10727
|
+
}
|
|
10728
|
+
testRuntimeId = localStartResult.runtimeId;
|
|
10729
|
+
}
|
|
10730
|
+
const resp = await fetch(normalizedAgentCardUrl, { headers, signal: AbortSignal.timeout(8e3) });
|
|
10731
|
+
if (!resp.ok) {
|
|
10732
|
+
const body = await readShortResponseBody(resp);
|
|
10733
|
+
const suffix = body ? `: ${body}` : "";
|
|
10734
|
+
return reply.send({ success: true, data: { reachable: false, error: `HTTP ${resp.status} ${resp.statusText}${suffix}` } });
|
|
10735
|
+
}
|
|
10736
|
+
const card = await resp.json();
|
|
10737
|
+
return reply.send({ success: true, data: { reachable: true, agentName: card.name, agentCard: card } });
|
|
10765
10738
|
} catch (error) {
|
|
10766
|
-
|
|
10767
|
-
|
|
10768
|
-
|
|
10769
|
-
|
|
10770
|
-
|
|
10771
|
-
|
|
10772
|
-
|
|
10739
|
+
return reply.send({
|
|
10740
|
+
success: true,
|
|
10741
|
+
data: {
|
|
10742
|
+
reachable: false,
|
|
10743
|
+
error: describeTestConnectionError(error, normalizedAgentCardUrl)
|
|
10744
|
+
}
|
|
10745
|
+
});
|
|
10746
|
+
} finally {
|
|
10747
|
+
if (testRuntimeId) {
|
|
10748
|
+
localA2AService.removeState(testRuntimeId);
|
|
10773
10749
|
}
|
|
10774
|
-
throw error;
|
|
10775
10750
|
}
|
|
10776
|
-
}
|
|
10777
|
-
|
|
10778
|
-
|
|
10779
|
-
|
|
10780
|
-
|
|
10781
|
-
|
|
10782
|
-
|
|
10783
|
-
|
|
10751
|
+
});
|
|
10752
|
+
}
|
|
10753
|
+
|
|
10754
|
+
// src/routes/task-routes.ts
|
|
10755
|
+
function registerTaskRoutes(app2, controller) {
|
|
10756
|
+
app2.get("/api/tasks", controller.list);
|
|
10757
|
+
app2.get("/api/tasks/:id", controller.get);
|
|
10758
|
+
app2.post("/api/tasks", controller.create);
|
|
10759
|
+
app2.put("/api/tasks/:id", controller.update);
|
|
10760
|
+
app2.patch("/api/tasks/:id", controller.update);
|
|
10761
|
+
app2.delete("/api/tasks/:id", controller.remove);
|
|
10762
|
+
app2.patch("/api/tasks/:id/complete", controller.complete);
|
|
10763
|
+
app2.get("/api/tasks/:id/work-items", controller.workItems);
|
|
10764
|
+
app2.post("/api/projects/:projectId/tasks/:id/reassign", controller.reassign);
|
|
10765
|
+
}
|
|
10766
|
+
|
|
10767
|
+
// src/routes/index.ts
|
|
10768
|
+
import {
|
|
10769
|
+
agentInstanceManager as agentInstanceManager6,
|
|
10770
|
+
agentLatticeManager as agentLatticeManager4,
|
|
10771
|
+
createProjectTaskReassignmentAuthorizer,
|
|
10772
|
+
createTaskLifecycleService,
|
|
10773
|
+
createTrustedAgentMessageDispatcher,
|
|
10774
|
+
getStoreLattice as getStoreLattice16,
|
|
10775
|
+
modelLatticeManager as modelLatticeManager3,
|
|
10776
|
+
ProjectTaskOwnerPolicy,
|
|
10777
|
+
projectMutationMutex as projectMutationMutex2,
|
|
10778
|
+
projectTaskAdmissionNotifier as projectTaskAdmissionNotifier2,
|
|
10779
|
+
taskLifecycleNotifier as taskLifecycleNotifier2
|
|
10780
|
+
} from "@axiom-lattice/core";
|
|
10781
|
+
|
|
10782
|
+
// src/controllers/tasks.ts
|
|
10783
|
+
import { snapshotExactRecord as snapshotExactRecord3 } from "@axiom-lattice/protocols";
|
|
10784
|
+
import { ProjectTaskOwnerPolicyError, projectTaskAdmissionNotifier } from "@axiom-lattice/core";
|
|
10784
10785
|
|
|
10785
10786
|
// src/services/collaboration/ProjectTaskThreadService.ts
|
|
10786
10787
|
import { isProjectLifecycleTask } from "@axiom-lattice/core";
|
|
@@ -11320,9 +11321,6 @@ function governedUpdate(task, body) {
|
|
|
11320
11321
|
if (has("ownerType") && body.ownerType !== task.ownerType || has("ownerId") && body.ownerId !== task.ownerId) {
|
|
11321
11322
|
return "TASK_OWNERSHIP_IMMUTABLE";
|
|
11322
11323
|
}
|
|
11323
|
-
if (task.ownerType !== "agent") return void 0;
|
|
11324
|
-
if (has("parentId") && body.parentId !== task.parentId || has("dependencies") || has("requireReview") || has("result") || has("failureReason") || has("status")) return "TASK_LIFECYCLE_TRANSITION_REQUIRED";
|
|
11325
|
-
if (has("context") || has("description")) return "COMPLETION_CONTEXT_MUTATION";
|
|
11326
11324
|
return void 0;
|
|
11327
11325
|
}
|
|
11328
11326
|
function lifecycleStatus(code) {
|
|
@@ -11763,7 +11761,8 @@ var AgentWebAppService = class {
|
|
|
11763
11761
|
integration: current.integration,
|
|
11764
11762
|
scope: { ...current.scope, ...patch.scope },
|
|
11765
11763
|
features: { ...current.features, ...patch.features },
|
|
11766
|
-
appearance: { ...current.appearance, ...patch.appearance }
|
|
11764
|
+
appearance: { ...current.appearance, ...patch.appearance },
|
|
11765
|
+
identity: patch.identity ?? current.identity
|
|
11767
11766
|
};
|
|
11768
11767
|
await this.validateAssistant(context, current.assistantId);
|
|
11769
11768
|
const validated = await this.validateConfig(context.tenantId, effective);
|
|
@@ -11772,7 +11771,8 @@ var AgentWebAppService = class {
|
|
|
11772
11771
|
...Object.prototype.hasOwnProperty.call(patch, "description") ? { description: validated.description } : {},
|
|
11773
11772
|
...patch.scope ? { scope: validated.scope } : {},
|
|
11774
11773
|
...patch.features ? { features: validated.features } : {},
|
|
11775
|
-
...patch.appearance ? { appearance: validated.appearance } : {}
|
|
11774
|
+
...patch.appearance ? { appearance: validated.appearance } : {},
|
|
11775
|
+
...patch.identity !== void 0 ? { identity: patch.identity } : {}
|
|
11776
11776
|
};
|
|
11777
11777
|
return this.writeSnapshot(context, current, update);
|
|
11778
11778
|
}
|
|
@@ -11885,13 +11885,28 @@ var agentWebAppFeaturesSchema = z4.object({
|
|
|
11885
11885
|
threadManagement: z4.boolean(),
|
|
11886
11886
|
attachments: z4.boolean(),
|
|
11887
11887
|
hitl: z4.boolean(),
|
|
11888
|
-
genUI: z4.boolean()
|
|
11888
|
+
genUI: z4.boolean(),
|
|
11889
|
+
files: z4.boolean().optional()
|
|
11889
11890
|
}).strict();
|
|
11890
11891
|
var agentWebAppAppearanceSchema = z4.object({
|
|
11891
11892
|
title: z4.string().max(120).optional(),
|
|
11892
11893
|
welcomeMessage: z4.string().max(1e3).optional(),
|
|
11893
11894
|
primaryColor: z4.string().regex(/^#[0-9A-Fa-f]{6}$/).optional()
|
|
11894
11895
|
}).strict();
|
|
11896
|
+
var agentWebAppIssuerSchema = z4.object({
|
|
11897
|
+
iss: z4.string().min(1).max(255),
|
|
11898
|
+
aud: z4.string().min(1).max(255),
|
|
11899
|
+
alg: z4.enum(["HS256", "RS256"]),
|
|
11900
|
+
secret: z4.string().min(1).max(512).optional(),
|
|
11901
|
+
jwksUrl: z4.string().url().max(2048).optional()
|
|
11902
|
+
}).strict().refine(
|
|
11903
|
+
(value) => value.alg === "HS256" ? value.secret !== void 0 : value.jwksUrl !== void 0,
|
|
11904
|
+
{ message: "HS256 requires secret; RS256 requires jwksUrl" }
|
|
11905
|
+
);
|
|
11906
|
+
var agentWebAppIdentitySchema = z4.object({
|
|
11907
|
+
policy: z4.enum(["unverified", "verified", "both"]),
|
|
11908
|
+
issuers: z4.array(agentWebAppIssuerSchema).min(1).max(10)
|
|
11909
|
+
}).strict();
|
|
11895
11910
|
var createAgentWebAppSchema = z4.object({
|
|
11896
11911
|
assistantId: externalId,
|
|
11897
11912
|
name: z4.string().min(1).max(120),
|
|
@@ -11899,14 +11914,16 @@ var createAgentWebAppSchema = z4.object({
|
|
|
11899
11914
|
integration: z4.object({ type: z4.literal("react_sdk") }).strict(),
|
|
11900
11915
|
scope: agentWebAppScopeSchema,
|
|
11901
11916
|
features: agentWebAppFeaturesSchema,
|
|
11902
|
-
appearance: agentWebAppAppearanceSchema
|
|
11917
|
+
appearance: agentWebAppAppearanceSchema,
|
|
11918
|
+
identity: agentWebAppIdentitySchema.optional()
|
|
11903
11919
|
}).strict();
|
|
11904
11920
|
var updateAgentWebAppSchema = z4.object({
|
|
11905
11921
|
name: z4.string().min(1).max(120).optional(),
|
|
11906
11922
|
description: z4.string().max(1e3).optional(),
|
|
11907
11923
|
scope: agentWebAppScopeSchema.partial().strict().optional(),
|
|
11908
11924
|
features: agentWebAppFeaturesSchema.partial().strict().optional(),
|
|
11909
|
-
appearance: agentWebAppAppearanceSchema.partial().strict().optional()
|
|
11925
|
+
appearance: agentWebAppAppearanceSchema.partial().strict().optional(),
|
|
11926
|
+
identity: agentWebAppIdentitySchema.optional()
|
|
11910
11927
|
}).strict().refine((value) => Object.keys(value).length > 0, "Patch must not be empty");
|
|
11911
11928
|
var listAgentWebAppsQuerySchema = z4.object({
|
|
11912
11929
|
assistantId: externalId.optional()
|
|
@@ -11993,6 +12010,7 @@ function invalidRequest(reply, statusCode) {
|
|
|
11993
12010
|
}
|
|
11994
12011
|
|
|
11995
12012
|
// src/routes/agent-web-app-runtime.ts
|
|
12013
|
+
import { createHmac as createHmac3 } from "crypto";
|
|
11996
12014
|
import { z as z5, ZodError as ZodError2 } from "zod";
|
|
11997
12015
|
|
|
11998
12016
|
// src/services/agent-web-app/AgentWebAppRuntimeService.ts
|
|
@@ -12013,7 +12031,16 @@ var AgentWebAppRuntimeService = class {
|
|
|
12013
12031
|
constructor(deps) {
|
|
12014
12032
|
this.deps = deps;
|
|
12015
12033
|
}
|
|
12016
|
-
|
|
12034
|
+
/** Identity policy configuration for the web app, or null when the app is missing/unconfigured. */
|
|
12035
|
+
async getIdentityConfig(webAppId) {
|
|
12036
|
+
try {
|
|
12037
|
+
const runtime = await this.resolveRuntime(webAppId, {});
|
|
12038
|
+
return runtime.webApp.identity ?? null;
|
|
12039
|
+
} catch {
|
|
12040
|
+
return null;
|
|
12041
|
+
}
|
|
12042
|
+
}
|
|
12043
|
+
async bootstrap(webAppId, userId, selection, identity) {
|
|
12017
12044
|
const runtime = await this.resolveRuntime(webAppId, selection);
|
|
12018
12045
|
const assistant = await this.deps.assistantResolver.resolve(
|
|
12019
12046
|
runtime.webApp.tenantId,
|
|
@@ -12046,7 +12073,7 @@ var AgentWebAppRuntimeService = class {
|
|
|
12046
12073
|
defaultModelKey: runtime.webApp.scope.defaultModelKey,
|
|
12047
12074
|
features: runtime.webApp.features,
|
|
12048
12075
|
appearance: runtime.webApp.appearance,
|
|
12049
|
-
identityAssurance: "unverified"
|
|
12076
|
+
identityAssurance: identity?.assurance ?? "unverified"
|
|
12050
12077
|
},
|
|
12051
12078
|
projects,
|
|
12052
12079
|
models
|
|
@@ -12067,6 +12094,14 @@ var AgentWebAppRuntimeService = class {
|
|
|
12067
12094
|
this.requireFeature(runtime.webApp.features.threadManagement);
|
|
12068
12095
|
return this.createOwnedThread(runtime, userId, input.label);
|
|
12069
12096
|
}
|
|
12097
|
+
/** Fetch one owned thread. */
|
|
12098
|
+
async getThread(webAppId, userId, threadId, selection) {
|
|
12099
|
+
const runtime = await this.resolveRuntime(webAppId, selection);
|
|
12100
|
+
await this.requireOwnedThread(runtime, userId, threadId);
|
|
12101
|
+
const thread = await this.deps.threadStore.getThreadById(runtime.webApp.tenantId, threadId);
|
|
12102
|
+
if (!thread) this.threadNotFound();
|
|
12103
|
+
return toRuntimeThread(thread);
|
|
12104
|
+
}
|
|
12070
12105
|
/** Update a thread label; omitted or blank labels preserve the existing label. */
|
|
12071
12106
|
async updateThread(webAppId, userId, threadId, input) {
|
|
12072
12107
|
const runtime = await this.resolveRuntime(webAppId, input);
|
|
@@ -12101,6 +12136,60 @@ var AgentWebAppRuntimeService = class {
|
|
|
12101
12136
|
return [{ id: message.id, role: message.role, ...content && (typeof content === "string" || content.length > 0) ? { content } : {} }];
|
|
12102
12137
|
});
|
|
12103
12138
|
}
|
|
12139
|
+
async getState(webAppId, userId, threadId, selection) {
|
|
12140
|
+
const runtime = await this.resolveRuntime(webAppId, selection);
|
|
12141
|
+
await this.requireOwnedThread(runtime, userId, threadId);
|
|
12142
|
+
const agent = this.resolveAgent(runtime, threadId);
|
|
12143
|
+
const state = await agent.getCurrentState();
|
|
12144
|
+
const pendingMessages = await agent.getPendingMessages();
|
|
12145
|
+
const normalized = pendingMessages.map((message) => ({
|
|
12146
|
+
...message,
|
|
12147
|
+
content: typeof message.content === "object" && message.content !== null ? "message" in message.content ? message.content.message : JSON.stringify(message.content) : String(message.content ?? "")
|
|
12148
|
+
}));
|
|
12149
|
+
return { ...state && typeof state === "object" ? state : {}, pendingMessages: normalized };
|
|
12150
|
+
}
|
|
12151
|
+
/** Raw stored messages (Console memory contract, tool/interrupt preserved). */
|
|
12152
|
+
async getConsoleMessages(webAppId, userId, threadId, selection) {
|
|
12153
|
+
const runtime = await this.resolveRuntime(webAppId, selection);
|
|
12154
|
+
await this.requireOwnedThread(runtime, userId, threadId);
|
|
12155
|
+
return this.deps.messageReader.getCurrentMessages({
|
|
12156
|
+
tenantId: runtime.webApp.tenantId,
|
|
12157
|
+
assistantId: runtime.webApp.assistantId,
|
|
12158
|
+
threadId,
|
|
12159
|
+
workspaceId: runtime.project.workspaceId,
|
|
12160
|
+
projectId: runtime.project.id
|
|
12161
|
+
});
|
|
12162
|
+
}
|
|
12163
|
+
/** Background enqueue only (ADR-026): returns the message id; delivery happens over resume_stream. */
|
|
12164
|
+
async enqueueMessage(webAppId, userId, threadId, content, selection) {
|
|
12165
|
+
const runtime = await this.resolveOwnedRuntime(webAppId, userId, threadId, selection);
|
|
12166
|
+
const agent = this.resolveAgent(runtime, threadId);
|
|
12167
|
+
const execution = await this.execute(agent, runtime, { input: { message: content } });
|
|
12168
|
+
return { messageId: execution.messageId };
|
|
12169
|
+
}
|
|
12170
|
+
async removePendingMessage(webAppId, userId, threadId, messageId, selection) {
|
|
12171
|
+
const runtime = await this.resolveRuntime(webAppId, selection);
|
|
12172
|
+
await this.requireOwnedThread(runtime, userId, threadId);
|
|
12173
|
+
const agent = this.resolveAgent(runtime, threadId);
|
|
12174
|
+
if (!await agent.removePendingMessage(messageId)) this.threadNotFound();
|
|
12175
|
+
}
|
|
12176
|
+
/** Thread-lifetime SSE resume from the last known client message id. */
|
|
12177
|
+
async resumeStream(webAppId, userId, threadId, messageId, selection) {
|
|
12178
|
+
const runtime = await this.resolveRuntime(webAppId, selection);
|
|
12179
|
+
await this.requireOwnedThread(runtime, userId, threadId);
|
|
12180
|
+
const agent = this.resolveAgent(runtime, threadId);
|
|
12181
|
+
return { stream: agent.chunkStream(messageId, []) };
|
|
12182
|
+
}
|
|
12183
|
+
async resolveFileScope(webAppId, selection) {
|
|
12184
|
+
const runtime = await this.resolveRuntime(webAppId, selection);
|
|
12185
|
+
return {
|
|
12186
|
+
tenantId: runtime.webApp.tenantId,
|
|
12187
|
+
workspaceId: runtime.project.workspaceId,
|
|
12188
|
+
projectId: runtime.project.id,
|
|
12189
|
+
assistantId: runtime.webApp.assistantId,
|
|
12190
|
+
enabled: runtime.webApp.features.files === true
|
|
12191
|
+
};
|
|
12192
|
+
}
|
|
12104
12193
|
async streamMessage(webAppId, userId, threadId, input) {
|
|
12105
12194
|
const runtime = await this.resolveOwnedRuntime(webAppId, userId, threadId, input);
|
|
12106
12195
|
const refs = this.resolveAttachmentRefs(runtime, userId, threadId, input.attachmentRefs ?? []);
|
|
@@ -12345,96 +12434,102 @@ function toRuntimeThread(thread) {
|
|
|
12345
12434
|
};
|
|
12346
12435
|
}
|
|
12347
12436
|
|
|
12348
|
-
// src/services/agent-web-app/
|
|
12349
|
-
import {
|
|
12350
|
-
|
|
12351
|
-
|
|
12352
|
-
|
|
12353
|
-
|
|
12354
|
-
return { startedToolIds: /* @__PURE__ */ new Set(), allowInterrupts: options.allowInterrupts ?? true, allowGenUI: options.allowGenUI ?? false };
|
|
12355
|
-
}
|
|
12356
|
-
function projectAgentWebAppChunk(chunk, context) {
|
|
12357
|
-
if (chunk.type === MessageChunkTypes4.AI) {
|
|
12358
|
-
const events = [];
|
|
12359
|
-
const startedToolIds = context?.startedToolIds ?? /* @__PURE__ */ new Set();
|
|
12360
|
-
if (typeof chunk.data.content === "string" && chunk.data.content) events.push({ type: "message.delta", text: chunk.data.content });
|
|
12361
|
-
if (context?.allowGenUI && Array.isArray(chunk.data.content)) {
|
|
12362
|
-
for (const value of chunk.data.content) {
|
|
12363
|
-
const block = parseAgentWebAppGenUIBlock2(value);
|
|
12364
|
-
if (block) events.push({ type: "genui.render", block });
|
|
12365
|
-
}
|
|
12366
|
-
}
|
|
12367
|
-
for (const call of chunk.data.tool_call_chunks ?? []) {
|
|
12368
|
-
if (call.id && call.name && !startedToolIds.has(call.id)) {
|
|
12369
|
-
startedToolIds.add(call.id);
|
|
12370
|
-
events.push({ type: "tool.started", id: call.id, name: call.name });
|
|
12371
|
-
}
|
|
12372
|
-
}
|
|
12373
|
-
for (const call of chunk.data.tool_calls ?? []) {
|
|
12374
|
-
if (!startedToolIds.has(call.id)) {
|
|
12375
|
-
startedToolIds.add(call.id);
|
|
12376
|
-
events.push({ type: "tool.started", id: call.id, name: call.name });
|
|
12377
|
-
}
|
|
12378
|
-
}
|
|
12379
|
-
return events;
|
|
12380
|
-
}
|
|
12381
|
-
if (chunk.type === MessageChunkTypes4.TOOL && chunk.data.tool_call_id) {
|
|
12382
|
-
return [{ type: "tool.completed", id: chunk.data.tool_call_id }];
|
|
12383
|
-
}
|
|
12384
|
-
if (chunk.type === MessageChunkTypes4.INTERRUPT) {
|
|
12385
|
-
if (context && !context.allowInterrupts) return [{ type: "stream.completed" }];
|
|
12386
|
-
const interrupt = publicInterrupt(chunk);
|
|
12387
|
-
return interrupt ? [{ type: "interrupt.created", interrupt }, { type: "stream.completed" }] : [{ type: "stream.completed" }];
|
|
12388
|
-
}
|
|
12389
|
-
if (chunk.type === MessageChunkTypes4.MESSAGE_COMPLETED) {
|
|
12390
|
-
return [
|
|
12391
|
-
{ type: "message.completed", messageId: chunk.data.id },
|
|
12392
|
-
{ type: "stream.completed" }
|
|
12393
|
-
];
|
|
12394
|
-
}
|
|
12395
|
-
if (chunk.type === MessageChunkTypes4.MESSAGE_FAILED) {
|
|
12396
|
-
return [
|
|
12397
|
-
{ type: "error", error: { code: "STREAM_FAILED", message: "Stream failed", retryable: true } },
|
|
12398
|
-
{ type: "stream.completed" }
|
|
12399
|
-
];
|
|
12437
|
+
// src/services/agent-web-app/AgentWebAppTokenVerifier.ts
|
|
12438
|
+
import { createHmac as createHmac2, createPublicKey, timingSafeEqual as timingSafeEqual2, verify as cryptoVerify } from "crypto";
|
|
12439
|
+
var WebAppTokenError = class extends Error {
|
|
12440
|
+
constructor(message) {
|
|
12441
|
+
super(message);
|
|
12442
|
+
this.name = "WebAppTokenError";
|
|
12400
12443
|
}
|
|
12401
|
-
|
|
12402
|
-
|
|
12403
|
-
|
|
12404
|
-
|
|
12405
|
-
|
|
12406
|
-
|
|
12407
|
-
const
|
|
12408
|
-
|
|
12409
|
-
return {
|
|
12410
|
-
id: id3,
|
|
12411
|
-
type: typeof value.type === "string" ? value.type : "input",
|
|
12412
|
-
prompt: typeof value.prompt === "string" ? value.prompt : typeof value.message === "string" ? value.message : "Input required",
|
|
12413
|
-
...isRecord3(value.data) ? { data: value.data } : {}
|
|
12414
|
-
};
|
|
12415
|
-
}
|
|
12416
|
-
function parseInterruptContent(content) {
|
|
12417
|
-
if (!content) return {};
|
|
12418
|
-
if (isRecord3(content)) return content;
|
|
12419
|
-
if (typeof content !== "string") return void 0;
|
|
12444
|
+
};
|
|
12445
|
+
var MAX_TOKEN_BYTES = 8 * 1024;
|
|
12446
|
+
var CLOCK_SKEW_SECONDS = 60;
|
|
12447
|
+
var JWKS_TTL_MS = 10 * 60 * 1e3;
|
|
12448
|
+
var jwksCache = /* @__PURE__ */ new Map();
|
|
12449
|
+
function decodeSegment(segment) {
|
|
12450
|
+
const json = Buffer.from(segment, "base64url").toString("utf8");
|
|
12451
|
+
let value;
|
|
12420
12452
|
try {
|
|
12421
|
-
|
|
12422
|
-
return isRecord3(parsed) ? parsed : { prompt: content };
|
|
12453
|
+
value = JSON.parse(json);
|
|
12423
12454
|
} catch {
|
|
12424
|
-
|
|
12455
|
+
throw new WebAppTokenError("Malformed token segment");
|
|
12425
12456
|
}
|
|
12457
|
+
if (typeof value !== "object" || value === null) throw new WebAppTokenError("Malformed token segment");
|
|
12458
|
+
return value;
|
|
12426
12459
|
}
|
|
12427
|
-
function
|
|
12428
|
-
|
|
12460
|
+
async function resolveKey(issuer, kid, fetchImpl) {
|
|
12461
|
+
const cacheKey = issuer.jwksUrl ?? "";
|
|
12462
|
+
const load = async () => {
|
|
12463
|
+
const response = await fetchImpl(issuer.jwksUrl ?? "");
|
|
12464
|
+
if (!response.ok) throw new WebAppTokenError("JWKS fetch failed");
|
|
12465
|
+
const document = await response.json();
|
|
12466
|
+
const keys2 = document.keys;
|
|
12467
|
+
const entry2 = { keys: Array.isArray(keys2) ? keys2 : [], fetchedAt: Date.now() };
|
|
12468
|
+
jwksCache.set(cacheKey, entry2);
|
|
12469
|
+
return entry2;
|
|
12470
|
+
};
|
|
12471
|
+
const cached = jwksCache.get(cacheKey);
|
|
12472
|
+
let entry = cached && Date.now() - cached.fetchedAt < JWKS_TTL_MS ? cached : await load();
|
|
12473
|
+
let jwk = entry.keys.find((key) => kid === void 0 ? true : key.kid === kid);
|
|
12474
|
+
if (!jwk && kid !== void 0) {
|
|
12475
|
+
entry = await load();
|
|
12476
|
+
jwk = entry.keys.find((key) => key.kid === kid);
|
|
12477
|
+
}
|
|
12478
|
+
if (!jwk) throw new WebAppTokenError("No matching JWKS key");
|
|
12479
|
+
return createPublicKey({ key: jwk, format: "jwk" });
|
|
12480
|
+
}
|
|
12481
|
+
async function verifyWebAppToken(token, issuer, options = {}) {
|
|
12482
|
+
const nowMs = options.now ? options.now() : Date.now();
|
|
12483
|
+
const now = Math.floor(nowMs / 1e3);
|
|
12484
|
+
if (token.length === 0 || Buffer.byteLength(token) > MAX_TOKEN_BYTES) throw new WebAppTokenError("Token size invalid");
|
|
12485
|
+
const parts = token.split(".");
|
|
12486
|
+
if (parts.length !== 3) throw new WebAppTokenError("Token structure invalid");
|
|
12487
|
+
const [rawHeader, rawPayload, rawSignature] = parts;
|
|
12488
|
+
const header2 = decodeSegment(rawHeader);
|
|
12489
|
+
const payload = decodeSegment(rawPayload);
|
|
12490
|
+
if (header2.alg !== issuer.alg) throw new WebAppTokenError("Token algorithm mismatch");
|
|
12491
|
+
if (payload.iss !== issuer.iss) throw new WebAppTokenError("Token issuer mismatch");
|
|
12492
|
+
if (payload.aud !== issuer.aud) throw new WebAppTokenError("Token audience mismatch");
|
|
12493
|
+
const exp = typeof payload.exp === "number" ? payload.exp : void 0;
|
|
12494
|
+
if (exp === void 0 || exp + CLOCK_SKEW_SECONDS < now) throw new WebAppTokenError("Token expired");
|
|
12495
|
+
const nbf = typeof payload.nbf === "number" ? payload.nbf : void 0;
|
|
12496
|
+
if (nbf !== void 0 && nbf - CLOCK_SKEW_SECONDS > now) throw new WebAppTokenError("Token not yet valid");
|
|
12497
|
+
const signature = Buffer.from(rawSignature, "base64url");
|
|
12498
|
+
const data = Buffer.from(`${rawHeader}.${rawPayload}`);
|
|
12499
|
+
if (issuer.alg === "HS256") {
|
|
12500
|
+
if (typeof issuer.secret !== "string" || issuer.secret.length === 0) throw new WebAppTokenError("Issuer secret missing");
|
|
12501
|
+
const expected = createHmac2("sha256", issuer.secret).update(data).digest();
|
|
12502
|
+
if (signature.length !== expected.length || !timingSafeEqual2(signature, expected)) {
|
|
12503
|
+
throw new WebAppTokenError("Token signature invalid");
|
|
12504
|
+
}
|
|
12505
|
+
} else {
|
|
12506
|
+
if (typeof issuer.jwksUrl !== "string") throw new WebAppTokenError("Issuer JWKS URL missing");
|
|
12507
|
+
const key = await resolveKey(issuer, typeof header2.kid === "string" ? header2.kid : void 0, options.fetchImpl ?? fetch);
|
|
12508
|
+
let valid = false;
|
|
12509
|
+
try {
|
|
12510
|
+
valid = cryptoVerify("RSA-SHA256", data, key, signature);
|
|
12511
|
+
} catch {
|
|
12512
|
+
valid = false;
|
|
12513
|
+
}
|
|
12514
|
+
if (!valid) throw new WebAppTokenError("Token signature invalid");
|
|
12515
|
+
}
|
|
12516
|
+
const sub = payload.sub;
|
|
12517
|
+
if (typeof sub !== "string" || sub.length === 0 || sub.length > 128 || !/^[A-Za-z0-9._:@-]+$/.test(sub)) {
|
|
12518
|
+
throw new WebAppTokenError("Token subject invalid");
|
|
12519
|
+
}
|
|
12520
|
+
return sub;
|
|
12429
12521
|
}
|
|
12430
12522
|
|
|
12431
12523
|
// src/routes/agent-web-app-runtime.ts
|
|
12524
|
+
import { createAgentWebAppStreamProjectionContext, projectAgentWebAppChunk } from "@axiom-lattice/protocols";
|
|
12432
12525
|
var idSchema = z5.string().min(1).max(128);
|
|
12433
12526
|
var paramsSchema = z5.object({ webAppId: idSchema }).strict();
|
|
12434
12527
|
var threadParamsSchema = z5.object({ webAppId: idSchema, threadId: idSchema }).strict();
|
|
12435
12528
|
var interruptParamsSchema = z5.object({ webAppId: idSchema, threadId: idSchema, interruptId: idSchema }).strict();
|
|
12529
|
+
var pendingMessageParamsSchema = z5.object({ webAppId: idSchema, threadId: idSchema, messageId: idSchema }).strict();
|
|
12436
12530
|
var bootstrapQuerySchema = z5.object({ projectId: idSchema.optional(), modelKey: idSchema.optional() }).strict();
|
|
12437
12531
|
var projectQuerySchema = z5.object({ projectId: idSchema.optional() }).strict();
|
|
12532
|
+
var projectQueryLenientSchema = projectQuerySchema.passthrough();
|
|
12438
12533
|
var threadBodySchema = z5.object({ projectId: idSchema.optional(), label: z5.string().max(128).optional() }).strict();
|
|
12439
12534
|
var streamBodySchema = z5.object({
|
|
12440
12535
|
content: z5.string().trim().min(1).max(32768),
|
|
@@ -12453,8 +12548,27 @@ var resumeBodySchema = z5.object({
|
|
|
12453
12548
|
])
|
|
12454
12549
|
}).strict();
|
|
12455
12550
|
var MAX_ATTACHMENT_BYTES = 10 * 1024 * 1024;
|
|
12551
|
+
var stateQuerySchema = z5.object({
|
|
12552
|
+
threadId: idSchema,
|
|
12553
|
+
projectId: idSchema.optional(),
|
|
12554
|
+
modelKey: idSchema.optional()
|
|
12555
|
+
}).strict();
|
|
12556
|
+
var resumeSchema = z5.object({
|
|
12557
|
+
messageId: idSchema,
|
|
12558
|
+
projectId: idSchema.optional(),
|
|
12559
|
+
modelKey: idSchema.optional()
|
|
12560
|
+
}).strict();
|
|
12561
|
+
var devTokenSchema = z5.object({
|
|
12562
|
+
iss: z5.string().min(1).max(255).optional(),
|
|
12563
|
+
sub: z5.string().regex(/^[A-Za-z0-9._:@-]+$/).max(128).optional(),
|
|
12564
|
+
hours: z5.number().gt(0).max(720).optional()
|
|
12565
|
+
}).strict();
|
|
12566
|
+
var fileQuerySchema = z5.object({
|
|
12567
|
+
projectId: idSchema.optional(),
|
|
12568
|
+
modelKey: idSchema.optional()
|
|
12569
|
+
}).passthrough();
|
|
12456
12570
|
function createExternalHeaderIdentityResolver() {
|
|
12457
|
-
return { resolve(request) {
|
|
12571
|
+
return { async resolve(request) {
|
|
12458
12572
|
const raw = request.headers["x-axiom-external-user-id"];
|
|
12459
12573
|
if (raw === void 0) throw new AgentWebAppRuntimeError("USER_ID_REQUIRED", 401, "External user id is required");
|
|
12460
12574
|
if (Array.isArray(raw) || typeof raw !== "string") throw new AgentWebAppRuntimeError("INVALID_USER_ID", 400, "External user id is invalid");
|
|
@@ -12463,47 +12577,109 @@ function createExternalHeaderIdentityResolver() {
|
|
|
12463
12577
|
return { userId, assurance: "unverified" };
|
|
12464
12578
|
} };
|
|
12465
12579
|
}
|
|
12580
|
+
function createWebAppIdentityResolver(deps) {
|
|
12581
|
+
const headerResolver = createExternalHeaderIdentityResolver();
|
|
12582
|
+
return {
|
|
12583
|
+
async resolve(request, webAppId) {
|
|
12584
|
+
let config;
|
|
12585
|
+
try {
|
|
12586
|
+
config = await deps.getIdentityConfig(webAppId);
|
|
12587
|
+
} catch {
|
|
12588
|
+
config = void 0;
|
|
12589
|
+
}
|
|
12590
|
+
const policy = config?.policy ?? "unverified";
|
|
12591
|
+
const authHeader = request.headers.authorization;
|
|
12592
|
+
const bearer = typeof authHeader === "string" && authHeader.startsWith("Bearer ") ? authHeader.slice(7).trim() : void 0;
|
|
12593
|
+
if (policy !== "unverified" && bearer !== void 0) {
|
|
12594
|
+
const issuers = config?.issuers ?? [];
|
|
12595
|
+
if (issuers.length === 0) throw new AgentWebAppRuntimeError("INVALID_REQUEST", 401, "No trusted issuer configured");
|
|
12596
|
+
let lastError = new AgentWebAppRuntimeError("INVALID_REQUEST", 401, "Invalid identity token");
|
|
12597
|
+
for (const issuer of issuers) {
|
|
12598
|
+
try {
|
|
12599
|
+
const sub = await verifyWebAppToken(bearer, issuer);
|
|
12600
|
+
return { userId: sub, assurance: "verified" };
|
|
12601
|
+
} catch (error) {
|
|
12602
|
+
if (error instanceof WebAppTokenError) {
|
|
12603
|
+
lastError = error;
|
|
12604
|
+
continue;
|
|
12605
|
+
}
|
|
12606
|
+
throw error;
|
|
12607
|
+
}
|
|
12608
|
+
}
|
|
12609
|
+
const message = lastError instanceof WebAppTokenError ? `Invalid identity token: ${lastError.message}` : "Invalid identity token";
|
|
12610
|
+
throw new AgentWebAppRuntimeError("INVALID_REQUEST", 401, message);
|
|
12611
|
+
}
|
|
12612
|
+
if (policy === "verified") throw new AgentWebAppRuntimeError("USER_ID_REQUIRED", 401, "A verified identity token is required");
|
|
12613
|
+
return headerResolver.resolve(request, webAppId);
|
|
12614
|
+
}
|
|
12615
|
+
};
|
|
12616
|
+
}
|
|
12466
12617
|
function registerAgentWebAppRuntimeRoutes(app2, deps) {
|
|
12467
|
-
const identities = deps.identityResolver ??
|
|
12618
|
+
const identities = deps.identityResolver ?? createWebAppIdentityResolver({ getIdentityConfig: (webAppId) => deps.service.getIdentityConfig(webAppId) });
|
|
12468
12619
|
app2.register(async (routes) => {
|
|
12469
12620
|
routes.setErrorHandler((error, _request, reply) => normalizeError2(reply, error));
|
|
12470
12621
|
routes.get("/api/web-apps/:webAppId/runtime/bootstrap", async (request, reply) => handle2(reply, async () => {
|
|
12471
12622
|
const { webAppId } = paramsSchema.parse(request.params);
|
|
12472
|
-
const identity = resolveRuntimeIdentity(request, webAppId, identities);
|
|
12473
|
-
return { data: await deps.service.bootstrap(webAppId, identity.userId, withPreview(bootstrapQuerySchema.parse(request.query), identity.preview)) };
|
|
12623
|
+
const identity = await resolveRuntimeIdentity(request, webAppId, identities);
|
|
12624
|
+
return { data: await deps.service.bootstrap(webAppId, identity.userId, withPreview(bootstrapQuerySchema.parse(request.query), identity.preview), { assurance: identity.assurance ?? "unverified" }) };
|
|
12474
12625
|
}));
|
|
12475
12626
|
routes.get("/api/web-apps/:webAppId/runtime/threads", async (request, reply) => handle2(reply, async () => {
|
|
12476
12627
|
const { webAppId } = paramsSchema.parse(request.params);
|
|
12477
|
-
const identity = resolveRuntimeIdentity(request, webAppId, identities);
|
|
12628
|
+
const identity = await resolveRuntimeIdentity(request, webAppId, identities);
|
|
12478
12629
|
const records = await deps.service.listThreads(webAppId, identity.userId, withPreview(projectQuerySchema.parse(request.query), identity.preview));
|
|
12479
12630
|
return { data: { records, total: records.length } };
|
|
12480
12631
|
}));
|
|
12481
12632
|
routes.post("/api/web-apps/:webAppId/runtime/threads", async (request, reply) => handle2(reply, async () => {
|
|
12482
12633
|
const { webAppId } = paramsSchema.parse(request.params);
|
|
12483
|
-
const identity = resolveRuntimeIdentity(request, webAppId, identities);
|
|
12634
|
+
const identity = await resolveRuntimeIdentity(request, webAppId, identities);
|
|
12484
12635
|
return { statusCode: 201, data: await deps.service.createThread(webAppId, identity.userId, withPreview(threadBodySchema.parse(request.body), identity.preview)) };
|
|
12485
12636
|
}));
|
|
12486
12637
|
routes.patch("/api/web-apps/:webAppId/runtime/threads/:threadId", async (request, reply) => handle2(reply, async () => {
|
|
12487
12638
|
const { webAppId, threadId } = threadParamsSchema.parse(request.params);
|
|
12488
|
-
const identity = resolveRuntimeIdentity(request, webAppId, identities);
|
|
12639
|
+
const identity = await resolveRuntimeIdentity(request, webAppId, identities);
|
|
12489
12640
|
return { data: await deps.service.updateThread(webAppId, identity.userId, threadId, withPreview(threadBodySchema.parse(request.body), identity.preview)) };
|
|
12490
12641
|
}));
|
|
12491
12642
|
routes.delete("/api/web-apps/:webAppId/runtime/threads/:threadId", async (request, reply) => handle2(reply, async () => {
|
|
12492
12643
|
const { webAppId, threadId } = threadParamsSchema.parse(request.params);
|
|
12493
|
-
const identity = resolveRuntimeIdentity(request, webAppId, identities);
|
|
12644
|
+
const identity = await resolveRuntimeIdentity(request, webAppId, identities);
|
|
12494
12645
|
await deps.service.deleteThread(webAppId, identity.userId, threadId, withPreview(projectQuerySchema.parse(request.query), identity.preview));
|
|
12495
12646
|
return { data: { id: threadId } };
|
|
12496
12647
|
}));
|
|
12497
12648
|
routes.get("/api/web-apps/:webAppId/runtime/threads/:threadId/messages", async (request, reply) => handle2(reply, async () => {
|
|
12498
12649
|
const { webAppId, threadId } = threadParamsSchema.parse(request.params);
|
|
12499
|
-
const identity = resolveRuntimeIdentity(request, webAppId, identities);
|
|
12650
|
+
const identity = await resolveRuntimeIdentity(request, webAppId, identities);
|
|
12500
12651
|
const messages = await deps.service.getMessages(webAppId, identity.userId, threadId, withPreview(projectQuerySchema.parse(request.query), identity.preview));
|
|
12501
12652
|
return { data: { messages } };
|
|
12502
12653
|
}));
|
|
12654
|
+
routes.get("/api/web-apps/:webAppId/runtime/state", async (request, reply) => handle2(reply, async () => {
|
|
12655
|
+
const { webAppId } = paramsSchema.parse(request.params);
|
|
12656
|
+
const identity = await resolveRuntimeIdentity(request, webAppId, identities);
|
|
12657
|
+
const { threadId, projectId, modelKey: modelKey2 } = stateQuerySchema.parse(request.query);
|
|
12658
|
+
const state = await deps.service.getState(webAppId, identity.userId, threadId, withPreview({ projectId, modelKey: modelKey2 }, identity.preview));
|
|
12659
|
+
return { data: state };
|
|
12660
|
+
}));
|
|
12661
|
+
routes.delete("/api/web-apps/:webAppId/runtime/threads/:threadId/pending-messages/:messageId", async (request, reply) => handle2(reply, async () => {
|
|
12662
|
+
const { webAppId, threadId, messageId } = pendingMessageParamsSchema.parse(request.params);
|
|
12663
|
+
const identity = await resolveRuntimeIdentity(request, webAppId, identities);
|
|
12664
|
+
await deps.service.removePendingMessage(webAppId, identity.userId, threadId, messageId, withPreview(projectQuerySchema.parse(request.query), identity.preview));
|
|
12665
|
+
return { data: { removed: true } };
|
|
12666
|
+
}));
|
|
12503
12667
|
routes.post("/api/web-apps/:webAppId/runtime/threads/:threadId/messages/stream", async (request, reply) => {
|
|
12504
12668
|
try {
|
|
12505
12669
|
const { webAppId, threadId } = threadParamsSchema.parse(request.params);
|
|
12506
|
-
const identity = resolveRuntimeIdentity(request, webAppId, identities);
|
|
12670
|
+
const identity = await resolveRuntimeIdentity(request, webAppId, identities);
|
|
12671
|
+
const input = streamBodySchema.parse(request.body);
|
|
12672
|
+
const result = await deps.service.streamMessage(webAppId, identity.userId, threadId, withPreview(input, identity.preview));
|
|
12673
|
+
beginSse(reply);
|
|
12674
|
+
await pipeNativeWebAppStream(reply.raw, result.stream);
|
|
12675
|
+
} catch (error) {
|
|
12676
|
+
return normalizeError2(reply, error);
|
|
12677
|
+
}
|
|
12678
|
+
});
|
|
12679
|
+
routes.post("/api/web-apps/:webAppId/runtime/v1/threads/:threadId/messages/stream", async (request, reply) => {
|
|
12680
|
+
try {
|
|
12681
|
+
const { webAppId, threadId } = threadParamsSchema.parse(request.params);
|
|
12682
|
+
const identity = await resolveRuntimeIdentity(request, webAppId, identities);
|
|
12507
12683
|
const input = streamBodySchema.parse(request.body);
|
|
12508
12684
|
const result = await deps.service.streamMessage(webAppId, identity.userId, threadId, withPreview(input, identity.preview));
|
|
12509
12685
|
beginSse(reply);
|
|
@@ -12512,15 +12688,27 @@ function registerAgentWebAppRuntimeRoutes(app2, deps) {
|
|
|
12512
12688
|
return normalizeError2(reply, error);
|
|
12513
12689
|
}
|
|
12514
12690
|
});
|
|
12691
|
+
routes.post("/api/web-apps/:webAppId/runtime/threads/:threadId/resume", async (request, reply) => {
|
|
12692
|
+
try {
|
|
12693
|
+
const { webAppId, threadId } = threadParamsSchema.parse(request.params);
|
|
12694
|
+
const identity = await resolveRuntimeIdentity(request, webAppId, identities);
|
|
12695
|
+
const { messageId, projectId, modelKey: modelKey2 } = resumeSchema.parse(request.body);
|
|
12696
|
+
const result = await deps.service.resumeStream(webAppId, identity.userId, threadId, messageId, withPreview({ projectId, modelKey: modelKey2 }, identity.preview));
|
|
12697
|
+
beginSse(reply);
|
|
12698
|
+
await pipeNativeWebAppStream(reply.raw, result.stream);
|
|
12699
|
+
} catch (error) {
|
|
12700
|
+
return normalizeError2(reply, error);
|
|
12701
|
+
}
|
|
12702
|
+
});
|
|
12515
12703
|
routes.post("/api/web-apps/:webAppId/runtime/threads/:threadId/abort", async (request, reply) => handle2(reply, async () => {
|
|
12516
12704
|
const { webAppId, threadId } = threadParamsSchema.parse(request.params);
|
|
12517
|
-
const identity = resolveRuntimeIdentity(request, webAppId, identities);
|
|
12705
|
+
const identity = await resolveRuntimeIdentity(request, webAppId, identities);
|
|
12518
12706
|
await deps.service.abort(webAppId, identity.userId, threadId, withPreview(projectQuerySchema.parse(request.query), identity.preview));
|
|
12519
12707
|
return { data: { aborted: true } };
|
|
12520
12708
|
}));
|
|
12521
12709
|
routes.post("/api/web-apps/:webAppId/runtime/threads/:threadId/attachments", async (request, reply) => handle2(reply, async () => {
|
|
12522
12710
|
const { webAppId, threadId } = threadParamsSchema.parse(request.params);
|
|
12523
|
-
const identity = resolveRuntimeIdentity(request, webAppId, identities);
|
|
12711
|
+
const identity = await resolveRuntimeIdentity(request, webAppId, identities);
|
|
12524
12712
|
if (!request.isMultipart()) throw new ZodError2([]);
|
|
12525
12713
|
const part = await request.file({ limits: { files: 1, fileSize: MAX_ATTACHMENT_BYTES } });
|
|
12526
12714
|
if (!part || !part.filename || part.filename.length > 255) throw new ZodError2([]);
|
|
@@ -12536,7 +12724,7 @@ function registerAgentWebAppRuntimeRoutes(app2, deps) {
|
|
|
12536
12724
|
routes.post("/api/web-apps/:webAppId/runtime/threads/:threadId/interrupts/:interruptId/resume", async (request, reply) => {
|
|
12537
12725
|
try {
|
|
12538
12726
|
const { webAppId, threadId, interruptId } = interruptParamsSchema.parse(request.params);
|
|
12539
|
-
const identity = resolveRuntimeIdentity(request, webAppId, identities);
|
|
12727
|
+
const identity = await resolveRuntimeIdentity(request, webAppId, identities);
|
|
12540
12728
|
const { projectId, modelKey: modelKey2, message, response } = resumeBodySchema.parse(request.body);
|
|
12541
12729
|
const result = await deps.service.resumeInterrupt(webAppId, identity.userId, threadId, interruptId, withPreview({ projectId, modelKey: modelKey2 }, identity.preview), { message, response });
|
|
12542
12730
|
beginSse(reply);
|
|
@@ -12545,13 +12733,257 @@ function registerAgentWebAppRuntimeRoutes(app2, deps) {
|
|
|
12545
12733
|
return normalizeError2(reply, error);
|
|
12546
12734
|
}
|
|
12547
12735
|
});
|
|
12736
|
+
routes.post("/api/web-apps/:webAppId/dev/token", async (request, reply) => handle2(reply, async () => {
|
|
12737
|
+
if (process.env.WEB_APP_DEV_TOKEN_ENABLED !== "true") {
|
|
12738
|
+
throw new AgentWebAppRuntimeError("FEATURE_DISABLED", 403, "Dev token issuance is disabled (set WEB_APP_DEV_TOKEN_ENABLED=true)");
|
|
12739
|
+
}
|
|
12740
|
+
const { webAppId } = paramsSchema.parse(request.params);
|
|
12741
|
+
const { iss, sub, hours } = devTokenSchema.parse(request.body ?? {});
|
|
12742
|
+
const config = await deps.service.getIdentityConfig(webAppId);
|
|
12743
|
+
const issuer = (config?.issuers ?? []).find((entry) => entry.alg === "HS256" && (iss === void 0 || entry.iss === iss));
|
|
12744
|
+
if (!issuer || typeof issuer.secret !== "string") {
|
|
12745
|
+
throw new AgentWebAppRuntimeError("INVALID_REQUEST", 400, "Web app has no HS256 issuer configured");
|
|
12746
|
+
}
|
|
12747
|
+
const now = Math.floor(Date.now() / 1e3);
|
|
12748
|
+
const expiresIn = (hours ?? 24) * 3600;
|
|
12749
|
+
const subject = sub ?? "dev-user";
|
|
12750
|
+
const b64url = (input) => Buffer.from(input).toString("base64url");
|
|
12751
|
+
const body = `${b64url(JSON.stringify({ alg: "HS256", typ: "JWT" }))}.${b64url(JSON.stringify({ sub: subject, iss: issuer.iss, aud: issuer.aud, iat: now, exp: now + expiresIn }))}`;
|
|
12752
|
+
const token = `${body}.${createHmac3("sha256", issuer.secret).update(body).digest("base64url")}`;
|
|
12753
|
+
return { data: { token, tokenType: "Bearer", expiresIn, subject } };
|
|
12754
|
+
}));
|
|
12755
|
+
if (deps.taskController) {
|
|
12756
|
+
const tasks = deps.taskController;
|
|
12757
|
+
const withSubject = (handler) => async (request, reply) => {
|
|
12758
|
+
const { webAppId } = paramsSchema.parse(request.params);
|
|
12759
|
+
const identity = await resolveRuntimeIdentity(request, webAppId, identities);
|
|
12760
|
+
const proxy = Object.create(request);
|
|
12761
|
+
proxy.headers = { ...request.headers, "x-user-id": identity.userId };
|
|
12762
|
+
return handler(proxy, reply);
|
|
12763
|
+
};
|
|
12764
|
+
routes.get("/api/web-apps/:webAppId/runtime/tasks", withSubject((request, reply) => tasks.list(request, reply)));
|
|
12765
|
+
routes.post("/api/web-apps/:webAppId/runtime/tasks", withSubject((request, reply) => tasks.create(request, reply)));
|
|
12766
|
+
routes.get("/api/web-apps/:webAppId/runtime/tasks/:taskId", withSubject((request, reply) => tasks.get(request, reply)));
|
|
12767
|
+
routes.put("/api/web-apps/:webAppId/runtime/tasks/:taskId", withSubject((request, reply) => tasks.update(request, reply)));
|
|
12768
|
+
routes.patch("/api/web-apps/:webAppId/runtime/tasks/:taskId", withSubject((request, reply) => tasks.update(request, reply)));
|
|
12769
|
+
routes.delete("/api/web-apps/:webAppId/runtime/tasks/:taskId", withSubject((request, reply) => tasks.remove(request, reply)));
|
|
12770
|
+
routes.patch("/api/web-apps/:webAppId/runtime/tasks/:taskId/complete", withSubject((request, reply) => tasks.complete(request, reply)));
|
|
12771
|
+
routes.get("/api/web-apps/:webAppId/runtime/tasks/:taskId/work-items", withSubject((request, reply) => tasks.workItems(request, reply)));
|
|
12772
|
+
}
|
|
12773
|
+
if (deps.workspaceController) {
|
|
12774
|
+
const workspace = deps.workspaceController;
|
|
12775
|
+
const fileScope = async (webAppId, selection) => {
|
|
12776
|
+
const scope = await deps.service.resolveFileScope(webAppId, selection);
|
|
12777
|
+
if (!scope.enabled) throw new AgentWebAppRuntimeError("FEATURE_DISABLED", 403, "Files are disabled for this web app");
|
|
12778
|
+
return scope;
|
|
12779
|
+
};
|
|
12780
|
+
const scopedRequest = (request, scope, params, query) => {
|
|
12781
|
+
const proxy = Object.create(request);
|
|
12782
|
+
proxy.params = params;
|
|
12783
|
+
proxy.query = query;
|
|
12784
|
+
proxy.headers = { ...request.headers, "x-tenant-id": scope.tenantId };
|
|
12785
|
+
return proxy;
|
|
12786
|
+
};
|
|
12787
|
+
const unwrap = (result) => ({ data: result.data });
|
|
12788
|
+
routes.get("/api/web-apps/:webAppId/runtime/files/listpath", async (request) => {
|
|
12789
|
+
const { webAppId } = paramsSchema.parse(request.params);
|
|
12790
|
+
const identity = await resolveRuntimeIdentity(request, webAppId, identities);
|
|
12791
|
+
const scope = await fileScope(webAppId, fileQuerySchema.parse(request.query));
|
|
12792
|
+
const query = { ...request.query, assistantId: scope.assistantId };
|
|
12793
|
+
const result = await workspace.listPath(scopedRequest(request, scope, { workspaceId: scope.workspaceId, projectId: scope.projectId }, query));
|
|
12794
|
+
return unwrap(result);
|
|
12795
|
+
});
|
|
12796
|
+
routes.get("/api/web-apps/:webAppId/runtime/files/readfile", async (request) => {
|
|
12797
|
+
const { webAppId } = paramsSchema.parse(request.params);
|
|
12798
|
+
const identity = await resolveRuntimeIdentity(request, webAppId, identities);
|
|
12799
|
+
const scope = await fileScope(webAppId, fileQuerySchema.parse(request.query));
|
|
12800
|
+
const query = { ...request.query, assistantId: scope.assistantId };
|
|
12801
|
+
const result = await workspace.readFile(scopedRequest(request, scope, { workspaceId: scope.workspaceId, projectId: scope.projectId }, query));
|
|
12802
|
+
return unwrap(result);
|
|
12803
|
+
});
|
|
12804
|
+
routes.post("/api/web-apps/:webAppId/runtime/files/uploadfile", async (request, reply) => {
|
|
12805
|
+
const { webAppId } = paramsSchema.parse(request.params);
|
|
12806
|
+
const identity = await resolveRuntimeIdentity(request, webAppId, identities);
|
|
12807
|
+
const scope = await fileScope(webAppId, fileQuerySchema.parse(request.query));
|
|
12808
|
+
const scoped = scopedRequest(request, scope, { workspaceId: scope.workspaceId, projectId: scope.projectId }, { assistantId: scope.assistantId });
|
|
12809
|
+
await workspace.uploadFile(scoped, reply);
|
|
12810
|
+
return reply;
|
|
12811
|
+
});
|
|
12812
|
+
routes.get("/api/web-apps/:webAppId/runtime/files/viewfile", async (request, reply) => {
|
|
12813
|
+
const { webAppId } = paramsSchema.parse(request.params);
|
|
12814
|
+
const identity = await resolveRuntimeIdentity(request, webAppId, identities);
|
|
12815
|
+
const scope = await fileScope(webAppId, fileQuerySchema.parse(request.query));
|
|
12816
|
+
const query = { ...request.query, assistantId: scope.assistantId };
|
|
12817
|
+
await workspace.viewFile(scopedRequest(request, scope, { workspaceId: scope.workspaceId, projectId: scope.projectId }, query), reply);
|
|
12818
|
+
return reply;
|
|
12819
|
+
});
|
|
12820
|
+
routes.get("/api/web-apps/:webAppId/runtime/files/downloadfile", async (request, reply) => {
|
|
12821
|
+
const { webAppId } = paramsSchema.parse(request.params);
|
|
12822
|
+
const identity = await resolveRuntimeIdentity(request, webAppId, identities);
|
|
12823
|
+
const scope = await fileScope(webAppId, fileQuerySchema.parse(request.query));
|
|
12824
|
+
const query = { ...request.query, assistantId: scope.assistantId };
|
|
12825
|
+
await workspace.downloadFile(scopedRequest(request, scope, { workspaceId: scope.workspaceId, projectId: scope.projectId }, query), reply);
|
|
12826
|
+
return reply;
|
|
12827
|
+
});
|
|
12828
|
+
routes.get("/api/web-apps/:webAppId/runtime/files/downloadfolder", async (request, reply) => {
|
|
12829
|
+
const { webAppId } = paramsSchema.parse(request.params);
|
|
12830
|
+
const identity = await resolveRuntimeIdentity(request, webAppId, identities);
|
|
12831
|
+
const scope = await fileScope(webAppId, fileQuerySchema.parse(request.query));
|
|
12832
|
+
const query = { ...request.query, assistantId: scope.assistantId };
|
|
12833
|
+
await workspace.downloadFolder(scopedRequest(request, scope, { workspaceId: scope.workspaceId, projectId: scope.projectId }, query), reply);
|
|
12834
|
+
return reply;
|
|
12835
|
+
});
|
|
12836
|
+
const genuiSandboxParams = z5.object({ webAppId: idSchema, assistantId: idSchema, threadId: idSchema }).strict();
|
|
12837
|
+
const delegateGenuiFile = (method) => async (request, reply) => {
|
|
12838
|
+
const { webAppId } = genuiSandboxParams.parse(request.params);
|
|
12839
|
+
const identity = await resolveRuntimeIdentity(request, webAppId, identities);
|
|
12840
|
+
const scope = await fileScope(webAppId, fileQuerySchema.parse(request.query));
|
|
12841
|
+
const query = { ...request.query, assistantId: scope.assistantId };
|
|
12842
|
+
await method(scopedRequest(request, scope, { workspaceId: scope.workspaceId, projectId: scope.projectId }, query), reply);
|
|
12843
|
+
return reply;
|
|
12844
|
+
};
|
|
12845
|
+
routes.get("/api/web-apps/:webAppId/runtime/assistants/:assistantId/threads/:threadId/sandbox/downloadfile", delegateGenuiFile(workspace.downloadFile.bind(workspace)));
|
|
12846
|
+
routes.get("/api/web-apps/:webAppId/runtime/assistants/:assistantId/threads/:threadId/sandbox/viewfile", delegateGenuiFile(workspace.viewFile.bind(workspace)));
|
|
12847
|
+
}
|
|
12848
|
+
const mirrorThreadParams = z5.object({ webAppId: idSchema, assistantId: idSchema, threadId: idSchema }).strict();
|
|
12849
|
+
const mirrorBody = z5.object({ metadata: z5.object({ label: z5.string().max(300).optional() }).strict().optional(), projectId: idSchema.optional(), modelKey: idSchema.optional() }).strict();
|
|
12850
|
+
const toConsoleThread = (thread, assistantId) => ({
|
|
12851
|
+
id: thread.id,
|
|
12852
|
+
assistantId,
|
|
12853
|
+
metadata: thread.label ? { label: thread.label } : {},
|
|
12854
|
+
createdAt: thread.createdAt,
|
|
12855
|
+
updatedAt: thread.updatedAt
|
|
12856
|
+
});
|
|
12857
|
+
const mirrorAssistantParams = z5.object({ webAppId: idSchema, assistantId: idSchema }).strict();
|
|
12858
|
+
const mirrorPendingParams = z5.object({ webAppId: idSchema, assistantId: idSchema, threadId: idSchema, messageId: idSchema }).strict();
|
|
12859
|
+
const registerMirror = (prefix) => {
|
|
12860
|
+
routes.get(`${prefix}/assistants/:assistantId/threads`, async (request, reply) => handle2(reply, async () => {
|
|
12861
|
+
const { webAppId, assistantId } = mirrorAssistantParams.parse(request.params);
|
|
12862
|
+
const identity = await resolveRuntimeIdentity(request, webAppId, identities);
|
|
12863
|
+
const threads = await deps.service.listThreads(webAppId, identity.userId, projectQueryLenientSchema.parse(request.query));
|
|
12864
|
+
return { data: { records: threads.map((thread) => toConsoleThread(thread, assistantId)) } };
|
|
12865
|
+
}));
|
|
12866
|
+
routes.post(`${prefix}/assistants/:assistantId/threads`, async (request, reply) => handle2(reply, async () => {
|
|
12867
|
+
const { webAppId, assistantId } = mirrorAssistantParams.parse(request.params);
|
|
12868
|
+
const identity = await resolveRuntimeIdentity(request, webAppId, identities);
|
|
12869
|
+
const body = mirrorBody.parse(request.body ?? {});
|
|
12870
|
+
const created = await deps.service.createThread(webAppId, identity.userId, { label: body.metadata?.label, ...body.projectId ? { projectId: body.projectId } : {} });
|
|
12871
|
+
return { statusCode: 201, data: { id: created.id } };
|
|
12872
|
+
}));
|
|
12873
|
+
routes.get(`${prefix}/assistants/:assistantId/threads/:threadId`, async (request, reply) => handle2(reply, async () => {
|
|
12874
|
+
const { webAppId, assistantId, threadId } = mirrorThreadParams.parse(request.params);
|
|
12875
|
+
const identity = await resolveRuntimeIdentity(request, webAppId, identities);
|
|
12876
|
+
const thread = await deps.service.getThread(webAppId, identity.userId, threadId, projectQueryLenientSchema.parse(request.query));
|
|
12877
|
+
return { data: toConsoleThread(thread, assistantId) };
|
|
12878
|
+
}));
|
|
12879
|
+
routes.put(`${prefix}/assistants/:assistantId/threads/:threadId`, async (request, reply) => handle2(reply, async () => {
|
|
12880
|
+
const { webAppId, assistantId, threadId } = mirrorThreadParams.parse(request.params);
|
|
12881
|
+
const identity = await resolveRuntimeIdentity(request, webAppId, identities);
|
|
12882
|
+
const body = mirrorBody.parse(request.body ?? {});
|
|
12883
|
+
const updated = await deps.service.updateThread(webAppId, identity.userId, threadId, { label: body.metadata?.label, ...body.projectId ? { projectId: body.projectId } : {} });
|
|
12884
|
+
return { data: toConsoleThread(updated, assistantId) };
|
|
12885
|
+
}));
|
|
12886
|
+
routes.delete(`${prefix}/assistants/:assistantId/threads/:threadId`, async (request, reply) => handle2(reply, async () => {
|
|
12887
|
+
const { webAppId, threadId } = mirrorThreadParams.parse(request.params);
|
|
12888
|
+
const identity = await resolveRuntimeIdentity(request, webAppId, identities);
|
|
12889
|
+
await deps.service.deleteThread(webAppId, identity.userId, threadId, projectQueryLenientSchema.parse(request.query));
|
|
12890
|
+
return { data: { success: true, message: "Thread deleted" } };
|
|
12891
|
+
}));
|
|
12892
|
+
routes.post(`${prefix}/assistants/:assistantId/threads/:threadId/sandbox/uploadfile`, async (request, reply) => handle2(reply, async () => {
|
|
12893
|
+
const { webAppId, threadId } = mirrorThreadParams.parse(request.params);
|
|
12894
|
+
const identity = await resolveRuntimeIdentity(request, webAppId, identities);
|
|
12895
|
+
if (!request.isMultipart()) throw new ZodError2([]);
|
|
12896
|
+
const part = await request.file({ limits: { files: 1, fileSize: MAX_ATTACHMENT_BYTES } });
|
|
12897
|
+
if (!part || !part.filename || part.filename.length > 255) throw new ZodError2([]);
|
|
12898
|
+
const bytes = await part.toBuffer();
|
|
12899
|
+
if (bytes.length === 0 || part.file.truncated) throw new ZodError2([]);
|
|
12900
|
+
const uploaded = await deps.service.uploadAttachment(webAppId, identity.userId, threadId, projectQueryLenientSchema.parse(request.query), {
|
|
12901
|
+
name: part.filename,
|
|
12902
|
+
mimeType: part.mimetype || "application/octet-stream",
|
|
12903
|
+
bytes
|
|
12904
|
+
});
|
|
12905
|
+
return { statusCode: 201, data: { id: uploaded.ref, path: uploaded.ref, name: uploaded.name, mimeType: uploaded.mimeType, size: uploaded.size } };
|
|
12906
|
+
}));
|
|
12907
|
+
routes.get(`${prefix}/assistants/:assistantId/:threadId/memory`, async (request) => {
|
|
12908
|
+
const { webAppId, threadId } = mirrorThreadParams.parse(request.params);
|
|
12909
|
+
const identity = await resolveRuntimeIdentity(request, webAppId, identities);
|
|
12910
|
+
const messages = await deps.service.getConsoleMessages(webAppId, identity.userId, threadId, projectQueryLenientSchema.parse(request.query));
|
|
12911
|
+
return messages;
|
|
12912
|
+
});
|
|
12913
|
+
routes.get(`${prefix}/assistants/:assistantId/:threadId/state`, async (request) => {
|
|
12914
|
+
const { webAppId, threadId } = mirrorThreadParams.parse(request.params);
|
|
12915
|
+
const identity = await resolveRuntimeIdentity(request, webAppId, identities);
|
|
12916
|
+
return deps.service.getState(webAppId, identity.userId, threadId, projectQueryLenientSchema.parse(request.query));
|
|
12917
|
+
});
|
|
12918
|
+
routes.delete(`${prefix}/assistants/:assistantId/threads/:threadId/pending-messages/:messageId`, async (request, reply) => handle2(reply, async () => {
|
|
12919
|
+
const { webAppId, threadId, messageId } = mirrorPendingParams.parse(request.params);
|
|
12920
|
+
const identity = await resolveRuntimeIdentity(request, webAppId, identities);
|
|
12921
|
+
await deps.service.removePendingMessage(webAppId, identity.userId, threadId, messageId, projectQueryLenientSchema.parse(request.query));
|
|
12922
|
+
return { data: { removed: true } };
|
|
12923
|
+
}));
|
|
12924
|
+
routes.post(`${prefix}/assistants/:assistantId/threads/:threadId/abort`, async (request, reply) => handle2(reply, async () => {
|
|
12925
|
+
const { webAppId, threadId } = mirrorThreadParams.parse(request.params);
|
|
12926
|
+
const identity = await resolveRuntimeIdentity(request, webAppId, identities);
|
|
12927
|
+
await deps.service.abort(webAppId, identity.userId, threadId, projectQueryLenientSchema.parse(request.query));
|
|
12928
|
+
return { data: { aborted: true } };
|
|
12929
|
+
}));
|
|
12930
|
+
const runsBodySchema = z5.object({
|
|
12931
|
+
thread_id: idSchema.optional(),
|
|
12932
|
+
assistant_id: idSchema.optional(),
|
|
12933
|
+
messages: z5.array(z5.object({ content: z5.string().min(1).max(32768) }).passthrough()).min(1).max(20),
|
|
12934
|
+
background: z5.boolean().optional(),
|
|
12935
|
+
streaming: z5.boolean().optional(),
|
|
12936
|
+
projectId: idSchema.optional(),
|
|
12937
|
+
modelKey: idSchema.optional()
|
|
12938
|
+
}).passthrough();
|
|
12939
|
+
routes.post(`${prefix}/runs`, async (request, reply) => {
|
|
12940
|
+
try {
|
|
12941
|
+
const { webAppId } = paramsSchema.parse(request.params);
|
|
12942
|
+
const identity = await resolveRuntimeIdentity(request, webAppId, identities);
|
|
12943
|
+
const body = runsBodySchema.parse(request.body ?? {});
|
|
12944
|
+
if (!body.background || body.streaming) {
|
|
12945
|
+
throw new AgentWebAppRuntimeError("INVALID_REQUEST", 400, "Mirror runs supports only background enqueue (stream via resume_stream)");
|
|
12946
|
+
}
|
|
12947
|
+
const threadId = body.thread_id ?? "";
|
|
12948
|
+
if (!threadId) throw new AgentWebAppRuntimeError("INVALID_REQUEST", 400, "thread_id is required");
|
|
12949
|
+
const content = body.messages.map((message) => message.content).join("\n");
|
|
12950
|
+
const { messageId } = await deps.service.enqueueMessage(webAppId, identity.userId, threadId, content, { projectId: body.projectId, modelKey: body.modelKey });
|
|
12951
|
+
return reply.code(202).send({ success: true, messageId, queued: true });
|
|
12952
|
+
} catch (error) {
|
|
12953
|
+
return normalizeError2(reply, error);
|
|
12954
|
+
}
|
|
12955
|
+
});
|
|
12956
|
+
const resumeStreamSchema2 = z5.object({
|
|
12957
|
+
assistant_id: idSchema.optional(),
|
|
12958
|
+
thread_id: idSchema,
|
|
12959
|
+
message_id: idSchema,
|
|
12960
|
+
known_content: z5.string().optional(),
|
|
12961
|
+
poll_interval: z5.number().optional(),
|
|
12962
|
+
projectId: idSchema.optional(),
|
|
12963
|
+
modelKey: idSchema.optional()
|
|
12964
|
+
}).strict();
|
|
12965
|
+
routes.post(`${prefix}/resume_stream`, async (request, reply) => {
|
|
12966
|
+
try {
|
|
12967
|
+
const { webAppId } = paramsSchema.parse(request.params);
|
|
12968
|
+
const identity = await resolveRuntimeIdentity(request, webAppId, identities);
|
|
12969
|
+
const body = resumeStreamSchema2.parse(request.body ?? {});
|
|
12970
|
+
const result = await deps.service.resumeStream(webAppId, identity.userId, body.thread_id, body.message_id, { projectId: body.projectId, modelKey: body.modelKey });
|
|
12971
|
+
beginSse(reply);
|
|
12972
|
+
await pipeNativeWebAppStream(reply.raw, result.stream);
|
|
12973
|
+
} catch (error) {
|
|
12974
|
+
return normalizeError2(reply, error);
|
|
12975
|
+
}
|
|
12976
|
+
});
|
|
12977
|
+
};
|
|
12978
|
+
registerMirror("/api/web-apps/:webAppId/runtime");
|
|
12979
|
+
registerMirror("/api/web-apps/:webAppId/runtime/api");
|
|
12548
12980
|
});
|
|
12549
12981
|
}
|
|
12550
|
-
function resolveRuntimeIdentity(request, webAppId, identities) {
|
|
12551
|
-
if (request.headers["x-axiom-web-app-preview"] !== "true") return identities.resolve(request);
|
|
12982
|
+
async function resolveRuntimeIdentity(request, webAppId, identities) {
|
|
12983
|
+
if (request.headers["x-axiom-web-app-preview"] !== "true") return identities.resolve(request, webAppId);
|
|
12552
12984
|
const user = request.user;
|
|
12553
12985
|
if (!user?.id || !user.tenantId) throw new AgentWebAppRuntimeError("USER_ID_REQUIRED", 401, "Authenticated Console preview is required");
|
|
12554
|
-
return { userId: `console-preview:${user.id}:${webAppId}`, preview: { tenantId: user.tenantId, userId: user.id } };
|
|
12986
|
+
return { userId: `console-preview:${user.id}:${webAppId}`, assurance: "unverified", preview: { tenantId: user.tenantId, userId: user.id } };
|
|
12555
12987
|
}
|
|
12556
12988
|
function withPreview(selection, preview) {
|
|
12557
12989
|
return { ...selection, ...preview ? { preview } : {} };
|
|
@@ -12566,13 +12998,22 @@ async function pipeAgentWebAppStream(raw, stream, options = {}) {
|
|
|
12566
12998
|
]
|
|
12567
12999
|
});
|
|
12568
13000
|
}
|
|
13001
|
+
async function pipeNativeWebAppStream(raw, stream) {
|
|
13002
|
+
await pipeAsyncIterableToSse(raw, stream, {
|
|
13003
|
+
onError: () => [{
|
|
13004
|
+
type: "error",
|
|
13005
|
+
data: { id: "", content: "Stream failed" }
|
|
13006
|
+
}]
|
|
13007
|
+
});
|
|
13008
|
+
}
|
|
12569
13009
|
function beginSse(reply) {
|
|
12570
13010
|
reply.hijack();
|
|
12571
13011
|
reply.raw.writeHead(200, {
|
|
12572
13012
|
"Content-Type": "text/event-stream; charset=utf-8",
|
|
12573
13013
|
"Cache-Control": "no-cache, no-transform",
|
|
12574
13014
|
Connection: "keep-alive",
|
|
12575
|
-
"X-Accel-Buffering": "no"
|
|
13015
|
+
"X-Accel-Buffering": "no",
|
|
13016
|
+
"Access-Control-Allow-Origin": "*"
|
|
12576
13017
|
});
|
|
12577
13018
|
}
|
|
12578
13019
|
async function handle2(reply, operation) {
|
|
@@ -16604,7 +17045,7 @@ function snapshotProjectableWorkItem(value) {
|
|
|
16604
17045
|
if (!row || !createdAt || typeof row.id !== "string" || !row.id || typeof row.taskId !== "string" || !row.taskId || typeof row.tenantId !== "string" || !row.tenantId || typeof row.workspaceId !== "string" || !row.workspaceId || typeof row.projectId !== "string" || !row.projectId || typeof row.actor !== "string" || !row.actor || typeof row.eventKey !== "string" || !row.eventKey || !PROJECT_TASK_LIFECYCLE_ACTIONS.includes(row.action) || row.threadId !== void 0 && typeof row.threadId !== "string" || row.summary !== void 0 && typeof row.summary !== "string" || row.attempt !== void 0 && (typeof row.attempt !== "number" || !Number.isFinite(row.attempt))) return void 0;
|
|
16605
17046
|
try {
|
|
16606
17047
|
const detail = row.detail === void 0 ? void 0 : snapshotJsonValue(row.detail);
|
|
16607
|
-
if (detail !== void 0 && !
|
|
17048
|
+
if (detail !== void 0 && !isRecord3(detail)) return void 0;
|
|
16608
17049
|
return {
|
|
16609
17050
|
id: row.id,
|
|
16610
17051
|
taskId: row.taskId,
|
|
@@ -16751,7 +17192,7 @@ function snapshotLifecycleCursor(value) {
|
|
|
16751
17192
|
const createdAt = cloneExactDate(row?.createdAt);
|
|
16752
17193
|
return row && typeof row.id === "string" && row.id.length > 0 && createdAt ? { id: row.id, createdAt } : void 0;
|
|
16753
17194
|
}
|
|
16754
|
-
function
|
|
17195
|
+
function isRecord3(value) {
|
|
16755
17196
|
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
16756
17197
|
}
|
|
16757
17198
|
|
|
@@ -18209,6 +18650,46 @@ var registerLatticeRoutes = (app2, channelDeps, options = {}) => {
|
|
|
18209
18650
|
})
|
|
18210
18651
|
});
|
|
18211
18652
|
registerAgentWebAppRuntimeRoutes(app2, {
|
|
18653
|
+
workspaceController: new WorkspaceController(createProjectCollaborationInitializer({
|
|
18654
|
+
membershipStore: projectMembershipStore,
|
|
18655
|
+
roomStore: projectRoomStore,
|
|
18656
|
+
userTenantLinkStore
|
|
18657
|
+
})),
|
|
18658
|
+
taskController: createTaskController({
|
|
18659
|
+
access: {
|
|
18660
|
+
requireProjectAccess: async (input) => {
|
|
18661
|
+
const apps = await webAppStore.list(input.tenantId);
|
|
18662
|
+
const allowed = apps.some((app3) => app3.status === "active" && app3.scope.allowedProjectIds.includes(input.projectId));
|
|
18663
|
+
if (!allowed) throw new ProjectCollaborationError("PROJECT_ACCESS_DENIED", 403, "Project is not exposed through this web app");
|
|
18664
|
+
const project = await projectStore.getProjectById(input.tenantId, input.projectId);
|
|
18665
|
+
if (!project) throw new ProjectCollaborationError("PROJECT_NOT_FOUND", 404, "Project not found");
|
|
18666
|
+
return {
|
|
18667
|
+
project,
|
|
18668
|
+
membership: {
|
|
18669
|
+
id: `webapp_guest_${input.projectId}_${input.userId}`,
|
|
18670
|
+
tenantId: input.tenantId,
|
|
18671
|
+
projectId: input.projectId,
|
|
18672
|
+
userId: input.userId,
|
|
18673
|
+
role: "member",
|
|
18674
|
+
status: "active",
|
|
18675
|
+
joinedAt: /* @__PURE__ */ new Date(),
|
|
18676
|
+
updatedAt: /* @__PURE__ */ new Date()
|
|
18677
|
+
}
|
|
18678
|
+
};
|
|
18679
|
+
}
|
|
18680
|
+
},
|
|
18681
|
+
tasks: taskStore,
|
|
18682
|
+
workItems: taskWorkItemStore,
|
|
18683
|
+
bots: projectBotMembershipStore,
|
|
18684
|
+
rooms: projectRoomStore,
|
|
18685
|
+
threadService: taskThreadService,
|
|
18686
|
+
reassignmentAuthorizer,
|
|
18687
|
+
lifecycle: taskLifecycle,
|
|
18688
|
+
projectMutationMutex: projectMutationMutex2,
|
|
18689
|
+
executionMutex: projectTaskComposition?.mutex ?? {
|
|
18690
|
+
runExclusive: (_scope, operation) => operation()
|
|
18691
|
+
}
|
|
18692
|
+
}),
|
|
18212
18693
|
service: new AgentWebAppRuntimeService({
|
|
18213
18694
|
webAppStore,
|
|
18214
18695
|
threadStore: getStoreLattice16("default", "thread").store,
|
|
@@ -18216,7 +18697,7 @@ var registerLatticeRoutes = (app2, channelDeps, options = {}) => {
|
|
|
18216
18697
|
assistantResolver: {
|
|
18217
18698
|
resolve: async (tenantId, assistantId) => {
|
|
18218
18699
|
const stored = await assistantStore.getAssistantById(tenantId, assistantId);
|
|
18219
|
-
if (stored) return stored;
|
|
18700
|
+
if (stored) return { id: stored.id, name: stored.name, description: stored.description };
|
|
18220
18701
|
await agentLatticeManager4.initializeStoredAssistantsForTenant(tenantId);
|
|
18221
18702
|
const config = agentLatticeManager4.getAgentConfigWithTenant(tenantId, assistantId);
|
|
18222
18703
|
return config ? { id: assistantId, name: config.name, description: config.description } : null;
|
|
@@ -21329,7 +21810,7 @@ var start = async (config) => {
|
|
|
21329
21810
|
});
|
|
21330
21811
|
try {
|
|
21331
21812
|
const { A2AAuthService, parseEnvKeys } = await import("./A2AAuthService-DK5X6VIL.mjs");
|
|
21332
|
-
const { registerA2AStandardRoutes } = await import("./a2a-standard-
|
|
21813
|
+
const { registerA2AStandardRoutes } = await import("./a2a-standard-WC2BSOFV.mjs");
|
|
21333
21814
|
const a2a = await import("./a2a-7N3WDAOP.mjs");
|
|
21334
21815
|
const a2aKeyStore = getStoreLattice27("default", "a2aApiKey").store;
|
|
21335
21816
|
const assistantStore = getStoreLattice27("default", "assistant").store;
|