open-managed-agents 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +93 -0
- package/README.md +248 -0
- package/bin/oma.mjs +499 -0
- package/dist/scripts/alpha-openai-compatible-fixture.mjs +134 -0
- package/dist/scripts/alpha-smoke-models.mjs +92 -0
- package/dist/scripts/alpha-smoke.mjs +581 -0
- package/dist/scripts/oma-doctor.js +230 -0
- package/dist/scripts/oma-models.js +299 -0
- package/dist/scripts/oma-workspaces.js +133 -0
- package/dist/src/control-plane/admin/auth.js +43 -0
- package/dist/src/control-plane/admin/routes.js +76 -0
- package/dist/src/control-plane/admin/service.js +102 -0
- package/dist/src/control-plane/admission.js +90 -0
- package/dist/src/control-plane/agents/routes.js +63 -0
- package/dist/src/control-plane/agents/service.js +687 -0
- package/dist/src/control-plane/agents/store.js +391 -0
- package/dist/src/control-plane/agents/types.js +1 -0
- package/dist/src/control-plane/api-constants.js +3 -0
- package/dist/src/control-plane/app.js +907 -0
- package/dist/src/control-plane/console/auth.js +68 -0
- package/dist/src/control-plane/console/static.js +112 -0
- package/dist/src/control-plane/deployment-runtime-config.js +328 -0
- package/dist/src/control-plane/deployment-runtime-event-coordinator.js +25 -0
- package/dist/src/control-plane/deployment-runtime-turn-guard.js +14 -0
- package/dist/src/control-plane/deployment-session-coordinator.js +25 -0
- package/dist/src/control-plane/deployment-session-output-coordinator.js +27 -0
- package/dist/src/control-plane/deployment-storage.js +354 -0
- package/dist/src/control-plane/egress/image.js +48 -0
- package/dist/src/control-plane/egress/policy.js +571 -0
- package/dist/src/control-plane/egress/presets.js +77 -0
- package/dist/src/control-plane/egress/proxy.js +72 -0
- package/dist/src/control-plane/egress/ssrf.js +106 -0
- package/dist/src/control-plane/egress/utils/debug.js +14 -0
- package/dist/src/control-plane/egress/vendor/http-proxy.js +335 -0
- package/dist/src/control-plane/egress/vendor/mitm-ca.js +226 -0
- package/dist/src/control-plane/egress/vendor/mitm-leaf.js +124 -0
- package/dist/src/control-plane/egress/vendor/parent-proxy.js +438 -0
- package/dist/src/control-plane/egress/vendor/request-filter.js +105 -0
- package/dist/src/control-plane/egress/vendor/sandbox-config.js +1 -0
- package/dist/src/control-plane/egress/vendor/tls-terminate-proxy.js +269 -0
- package/dist/src/control-plane/environments/routes.js +54 -0
- package/dist/src/control-plane/environments/service.js +112 -0
- package/dist/src/control-plane/environments/store.js +132 -0
- package/dist/src/control-plane/environments/types.js +1 -0
- package/dist/src/control-plane/errors.js +81 -0
- package/dist/src/control-plane/events/broadcaster.js +217 -0
- package/dist/src/control-plane/events/constants.js +2 -0
- package/dist/src/control-plane/events/persist.js +39 -0
- package/dist/src/control-plane/events/request.js +190 -0
- package/dist/src/control-plane/events/routes.js +159 -0
- package/dist/src/control-plane/events/runtime-helpers.js +134 -0
- package/dist/src/control-plane/events/service.js +1985 -0
- package/dist/src/control-plane/events/session-guards.js +29 -0
- package/dist/src/control-plane/events/sse.js +6 -0
- package/dist/src/control-plane/events/store.js +772 -0
- package/dist/src/control-plane/events/tool-persistence.js +102 -0
- package/dist/src/control-plane/events/types.js +21 -0
- package/dist/src/control-plane/files/routes.js +121 -0
- package/dist/src/control-plane/files/service.js +65 -0
- package/dist/src/control-plane/files/store-common.js +87 -0
- package/dist/src/control-plane/files/store-local.js +488 -0
- package/dist/src/control-plane/files/store-memory.js +270 -0
- package/dist/src/control-plane/files/store.js +2 -0
- package/dist/src/control-plane/files/types.js +7 -0
- package/dist/src/control-plane/http.js +25 -0
- package/dist/src/control-plane/ids.js +43 -0
- package/dist/src/control-plane/logging.js +263 -0
- package/dist/src/control-plane/models/auth-storage-backend.js +238 -0
- package/dist/src/control-plane/models/catalog.js +78 -0
- package/dist/src/control-plane/models/config-security.js +376 -0
- package/dist/src/control-plane/models/deployment-config.js +93 -0
- package/dist/src/control-plane/models/routes.js +32 -0
- package/dist/src/control-plane/models/service.js +138 -0
- package/dist/src/control-plane/observability/instruments.js +65 -0
- package/dist/src/control-plane/observability/metrics.js +125 -0
- package/dist/src/control-plane/observability/routes.js +95 -0
- package/dist/src/control-plane/openapi/document.js +282 -0
- package/dist/src/control-plane/openapi/routes.js +78 -0
- package/dist/src/control-plane/request-idempotency.js +85 -0
- package/dist/src/control-plane/secrets/envelope.js +132 -0
- package/dist/src/control-plane/secrets/master-key.js +44 -0
- package/dist/src/control-plane/secrets/routes.js +20 -0
- package/dist/src/control-plane/secrets/service.js +71 -0
- package/dist/src/control-plane/secrets/store.js +212 -0
- package/dist/src/control-plane/secrets/types.js +3 -0
- package/dist/src/control-plane/sessions/pi/custom-tools.js +146 -0
- package/dist/src/control-plane/sessions/pi/mcp/bridge.js +527 -0
- package/dist/src/control-plane/sessions/pi/mcp/client.js +157 -0
- package/dist/src/control-plane/sessions/pi/mcp/credential.js +28 -0
- package/dist/src/control-plane/sessions/pi/mcp/fetch.js +69 -0
- package/dist/src/control-plane/sessions/pi/mcp/probe.js +202 -0
- package/dist/src/control-plane/sessions/pi/mcp/runtime.js +13 -0
- package/dist/src/control-plane/sessions/pi/runner.js +1104 -0
- package/dist/src/control-plane/sessions/pi/sandbox/cma-glob.js +388 -0
- package/dist/src/control-plane/sessions/pi/sandbox/cma-grep.js +124 -0
- package/dist/src/control-plane/sessions/pi/sandbox/docker-egress.js +347 -0
- package/dist/src/control-plane/sessions/pi/sandbox/docker.js +1589 -0
- package/dist/src/control-plane/sessions/pi/sandbox/glob.js +48 -0
- package/dist/src/control-plane/sessions/pi/sandbox/image.js +7 -0
- package/dist/src/control-plane/sessions/pi/sandbox/microsandbox.js +1677 -0
- package/dist/src/control-plane/sessions/pi/sandbox/provider.js +561 -0
- package/dist/src/control-plane/sessions/pi/sandbox/selection.js +168 -0
- package/dist/src/control-plane/sessions/pi/span-normalizer.js +82 -0
- package/dist/src/control-plane/sessions/pi/tool-permissions.js +392 -0
- package/dist/src/control-plane/sessions/pi/translator.js +100 -0
- package/dist/src/control-plane/sessions/request.js +164 -0
- package/dist/src/control-plane/sessions/resources.js +63 -0
- package/dist/src/control-plane/sessions/routes.js +88 -0
- package/dist/src/control-plane/sessions/serialize.js +24 -0
- package/dist/src/control-plane/sessions/service.js +733 -0
- package/dist/src/control-plane/sessions/store.js +538 -0
- package/dist/src/control-plane/sessions/types.js +1 -0
- package/dist/src/control-plane/skills/archive.js +169 -0
- package/dist/src/control-plane/skills/routes.js +41 -0
- package/dist/src/control-plane/skills/service.js +29 -0
- package/dist/src/control-plane/skills/store.js +233 -0
- package/dist/src/control-plane/skills/types.js +5 -0
- package/dist/src/control-plane/sqlite-transaction.js +15 -0
- package/dist/src/control-plane/vaults/mcp-oauth-validate.js +165 -0
- package/dist/src/control-plane/vaults/oauth-refresh-ticker.js +43 -0
- package/dist/src/control-plane/vaults/oauth-refresh.js +448 -0
- package/dist/src/control-plane/vaults/routes.js +66 -0
- package/dist/src/control-plane/vaults/service.js +553 -0
- package/dist/src/control-plane/vaults/store.js +691 -0
- package/dist/src/control-plane/vaults/types.js +1 -0
- package/dist/src/control-plane/wake-loop.js +65 -0
- package/dist/src/control-plane/wiring.js +92 -0
- package/dist/src/control-plane/workspace.js +4 -0
- package/dist/src/control-plane/workspaces/store.js +158 -0
- package/dist/src/egress-proxy-main.js +103 -0
- package/dist/src/main.js +124 -0
- package/dist/src/types/agents.js +1 -0
- package/dist/src/types/common.js +1 -0
- package/dist/src/types/environments.js +1 -0
- package/dist/src/types/events.js +121 -0
- package/dist/src/types/files.js +1 -0
- package/dist/src/types/json.js +19 -0
- package/dist/src/types/sessions.js +1 -0
- package/dist/ui/managed-agents-console/README.md +52 -0
- package/dist/ui/managed-agents-console/docs/agents.md +23 -0
- package/dist/ui/managed-agents-console/docs/console.md +23 -0
- package/dist/ui/managed-agents-console/docs/dreams.md +5 -0
- package/dist/ui/managed-agents-console/docs/environments.md +27 -0
- package/dist/ui/managed-agents-console/docs/events.md +19 -0
- package/dist/ui/managed-agents-console/docs/files.md +17 -0
- package/dist/ui/managed-agents-console/docs/github.md +11 -0
- package/dist/ui/managed-agents-console/docs/integrations.md +19 -0
- package/dist/ui/managed-agents-console/docs/memory.md +11 -0
- package/dist/ui/managed-agents-console/docs/migration.md +17 -0
- package/dist/ui/managed-agents-console/docs/multiagent.md +11 -0
- package/dist/ui/managed-agents-console/docs/outcomes.md +11 -0
- package/dist/ui/managed-agents-console/docs/overview.md +41 -0
- package/dist/ui/managed-agents-console/docs/permissions.md +24 -0
- package/dist/ui/managed-agents-console/docs/quickstart.md +51 -0
- package/dist/ui/managed-agents-console/docs/reference.md +27 -0
- package/dist/ui/managed-agents-console/docs/sandbox-reference.md +19 -0
- package/dist/ui/managed-agents-console/docs/sandbox-security.md +15 -0
- package/dist/ui/managed-agents-console/docs/scheduled-deployments.md +5 -0
- package/dist/ui/managed-agents-console/docs/self-hosted-sandboxes.md +15 -0
- package/dist/ui/managed-agents-console/docs/session-operations.md +21 -0
- package/dist/ui/managed-agents-console/docs/sessions.md +21 -0
- package/dist/ui/managed-agents-console/docs/skills.md +15 -0
- package/dist/ui/managed-agents-console/docs/tools.md +23 -0
- package/dist/ui/managed-agents-console/docs/vaults.md +19 -0
- package/dist/ui/managed-agents-console/docs/webhooks.md +11 -0
- package/dist/ui/managed-agents-console/index.html +47 -0
- package/dist/ui/managed-agents-console/serve.mjs +116 -0
- package/dist/ui/managed-agents-console/src/agents-files.jsx +273 -0
- package/dist/ui/managed-agents-console/src/api.js +1043 -0
- package/dist/ui/managed-agents-console/src/app.jsx +633 -0
- package/dist/ui/managed-agents-console/src/auth.jsx +215 -0
- package/dist/ui/managed-agents-console/src/console.css +733 -0
- package/dist/ui/managed-agents-console/src/data.js +188 -0
- package/dist/ui/managed-agents-console/src/detail.jsx +719 -0
- package/dist/ui/managed-agents-console/src/docs.jsx +200 -0
- package/dist/ui/managed-agents-console/src/environments.jsx +365 -0
- package/dist/ui/managed-agents-console/src/forms.jsx +494 -0
- package/dist/ui/managed-agents-console/src/icons.jsx +48 -0
- package/dist/ui/managed-agents-console/src/skills.jsx +21 -0
- package/dist/ui/managed-agents-console/src/sse.js +185 -0
- package/dist/ui/managed-agents-console/src/states.jsx +90 -0
- package/dist/ui/managed-agents-console/src/tweaks-panel.jsx +541 -0
- package/dist/ui/managed-agents-console/src/ui.jsx +190 -0
- package/dist/ui/managed-agents-console/src/vaults-data.js +89 -0
- package/dist/ui/managed-agents-console/src/vaults.jsx +171 -0
- package/dist/ui/managed-agents-console/vendor/babel.min.js +4 -0
- package/dist/ui/managed-agents-console/vendor/react-dom.production.min.js +267 -0
- package/dist/ui/managed-agents-console/vendor/react.production.min.js +31 -0
- package/dist/ui/openapi-docs/VENDOR.md +27 -0
- package/dist/ui/openapi-docs/index.html +16 -0
- package/dist/ui/openapi-docs/swagger-initializer.js +14 -0
- package/dist/ui/openapi-docs/vendor/LICENSE +202 -0
- package/dist/ui/openapi-docs/vendor/NOTICE +2 -0
- package/dist/ui/openapi-docs/vendor/swagger-ui-bundle.js +2 -0
- package/dist/ui/openapi-docs/vendor/swagger-ui-standalone-preset.js +2 -0
- package/dist/ui/openapi-docs/vendor/swagger-ui.css +3 -0
- package/package.json +53 -0
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import { createHash, randomBytes, timingSafeEqual } from "node:crypto";
|
|
2
|
+
import { readFileSync } from "node:fs";
|
|
3
|
+
export const ADMIN_KEY_ENV = "OMA_ADMIN_KEY";
|
|
4
|
+
export const ADMIN_KEY_FILE_ENV = "OMA_ADMIN_KEY_FILE";
|
|
5
|
+
const ADMIN_KEY_BYTES = 32;
|
|
6
|
+
export function generateAdminKey() {
|
|
7
|
+
return randomBytes(ADMIN_KEY_BYTES).toString("base64");
|
|
8
|
+
}
|
|
9
|
+
export function loadAdminKey(env = process.env) {
|
|
10
|
+
const direct = env[ADMIN_KEY_ENV];
|
|
11
|
+
const file = env[ADMIN_KEY_FILE_ENV];
|
|
12
|
+
if (direct !== undefined && file !== undefined) {
|
|
13
|
+
throw new Error(`set exactly one of ${ADMIN_KEY_ENV} or ${ADMIN_KEY_FILE_ENV}, not both`);
|
|
14
|
+
}
|
|
15
|
+
if (direct === undefined && file === undefined)
|
|
16
|
+
return undefined;
|
|
17
|
+
const value = direct === undefined ? readFileSync(file, "utf8").trim() : direct.trim();
|
|
18
|
+
parseAdminKey(value, direct === undefined ? ADMIN_KEY_FILE_ENV : ADMIN_KEY_ENV);
|
|
19
|
+
return value;
|
|
20
|
+
}
|
|
21
|
+
function parseAdminKey(value, source) {
|
|
22
|
+
const decoded = Buffer.from(value, "base64");
|
|
23
|
+
if (decoded.length !== ADMIN_KEY_BYTES ||
|
|
24
|
+
decoded.toString("base64") !== value) {
|
|
25
|
+
throw new Error(`${source} must be exactly ${ADMIN_KEY_BYTES} random bytes, ` +
|
|
26
|
+
`base64-encoded (generate one with: node -e "console.log(` +
|
|
27
|
+
`require('crypto').randomBytes(32).toString('base64'))")`);
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
export function createAdminAuth(adminKey) {
|
|
31
|
+
const expectedDigest = sha256(adminKey);
|
|
32
|
+
return {
|
|
33
|
+
verify(presented) {
|
|
34
|
+
return timingSafeEqual(sha256(presented), expectedDigest);
|
|
35
|
+
},
|
|
36
|
+
fingerprint() {
|
|
37
|
+
return expectedDigest.toString("hex");
|
|
38
|
+
},
|
|
39
|
+
};
|
|
40
|
+
}
|
|
41
|
+
function sha256(value) {
|
|
42
|
+
return createHash("sha256").update(value).digest();
|
|
43
|
+
}
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
import { Hono } from "hono";
|
|
2
|
+
import { invalidRequest } from "../errors.js";
|
|
3
|
+
import { parseJsonBody, parseLimit } from "../http.js";
|
|
4
|
+
import { log } from "../logging.js";
|
|
5
|
+
export function adminRoutes(service) {
|
|
6
|
+
const app = new Hono();
|
|
7
|
+
app.post("/workspaces", async (c) => {
|
|
8
|
+
const body = await parseJsonBody(c.req);
|
|
9
|
+
const workspace = service.createWorkspace(body);
|
|
10
|
+
emitAdminAudit(c.get("requestId"), {
|
|
11
|
+
action: "create_workspace",
|
|
12
|
+
workspace_id: workspace.id,
|
|
13
|
+
});
|
|
14
|
+
return c.json(workspace, 201);
|
|
15
|
+
});
|
|
16
|
+
app.get("/workspaces", (c) => {
|
|
17
|
+
return c.json(service.listWorkspaces(), 200);
|
|
18
|
+
});
|
|
19
|
+
app.get("/workspaces/:id", (c) => {
|
|
20
|
+
return c.json(service.getWorkspace(c.req.param("id")), 200);
|
|
21
|
+
});
|
|
22
|
+
app.post("/workspaces/:id/keys", async (c) => {
|
|
23
|
+
const body = await parseOptionalJsonBody(c.req);
|
|
24
|
+
const minted = service.mintKey(c.req.param("id"), body);
|
|
25
|
+
emitAdminAudit(c.get("requestId"), {
|
|
26
|
+
action: "mint_key",
|
|
27
|
+
workspace_id: minted.workspace_id,
|
|
28
|
+
key_sha256: minted.key_sha256,
|
|
29
|
+
});
|
|
30
|
+
return c.json(minted, 201);
|
|
31
|
+
});
|
|
32
|
+
app.get("/workspaces/:id/keys", (c) => {
|
|
33
|
+
return c.json(service.listKeys(c.req.param("id")), 200);
|
|
34
|
+
});
|
|
35
|
+
app.get("/workspaces/:id/mcp-credentials", (c) => {
|
|
36
|
+
const limit = parseLimit(c.req.query("limit"));
|
|
37
|
+
const page = c.req.query("page") || undefined;
|
|
38
|
+
return c.json(service.listWorkspaceCredentialMetadata(c.req.param("id"), {
|
|
39
|
+
...(limit === undefined ? {} : { limit }),
|
|
40
|
+
...(page === undefined ? {} : { page }),
|
|
41
|
+
}), 200);
|
|
42
|
+
});
|
|
43
|
+
app.delete("/keys/:sha256", (c) => {
|
|
44
|
+
const revoked = service.revokeKey(c.req.param("sha256"));
|
|
45
|
+
emitAdminAudit(c.get("requestId"), {
|
|
46
|
+
action: "revoke_key",
|
|
47
|
+
workspace_id: revoked.workspace_id,
|
|
48
|
+
key_sha256: revoked.key_sha256,
|
|
49
|
+
revoked_at: revoked.revoked_at,
|
|
50
|
+
});
|
|
51
|
+
return c.json(revoked, 200);
|
|
52
|
+
});
|
|
53
|
+
return app;
|
|
54
|
+
}
|
|
55
|
+
function emitAdminAudit(requestId, event) {
|
|
56
|
+
// Wire shape predates the structured logger; `type: "admin_audit"` is the
|
|
57
|
+
// grep contract and stays (0121 C1 — the logger adds ts/level/event around
|
|
58
|
+
// it). Emitted at the audit level: OMA_LOG_LEVEL must never be able to
|
|
59
|
+
// silence the admin audit trail.
|
|
60
|
+
log.audit("admin_audit", {
|
|
61
|
+
type: "admin_audit",
|
|
62
|
+
request_id: requestId,
|
|
63
|
+
...event,
|
|
64
|
+
});
|
|
65
|
+
}
|
|
66
|
+
async function parseOptionalJsonBody(req) {
|
|
67
|
+
const text = await req.text();
|
|
68
|
+
if (text.trim() === "")
|
|
69
|
+
return undefined;
|
|
70
|
+
try {
|
|
71
|
+
return JSON.parse(text);
|
|
72
|
+
}
|
|
73
|
+
catch (error) {
|
|
74
|
+
throw invalidRequest("Request body must be valid JSON", String(error));
|
|
75
|
+
}
|
|
76
|
+
}
|
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
import { invalidRequest, notFound } from "../errors.js";
|
|
2
|
+
export class DefaultAdminService {
|
|
3
|
+
workspaces;
|
|
4
|
+
vaults;
|
|
5
|
+
constructor(workspaces, vaults) {
|
|
6
|
+
this.workspaces = workspaces;
|
|
7
|
+
this.vaults = vaults;
|
|
8
|
+
}
|
|
9
|
+
createWorkspace(input) {
|
|
10
|
+
const { name } = parseCreateWorkspace(input);
|
|
11
|
+
return toAdminWorkspace(this.workspaces.createWorkspace(name));
|
|
12
|
+
}
|
|
13
|
+
listWorkspaces() {
|
|
14
|
+
return this.workspaces.listWorkspaces().map(toAdminWorkspace);
|
|
15
|
+
}
|
|
16
|
+
getWorkspace(workspaceId) {
|
|
17
|
+
const row = this.workspaces.getWorkspace(workspaceId);
|
|
18
|
+
if (!row)
|
|
19
|
+
throw notFound(`Workspace not found: ${workspaceId}`);
|
|
20
|
+
return toAdminWorkspace(row);
|
|
21
|
+
}
|
|
22
|
+
mintKey(workspaceId, input) {
|
|
23
|
+
const { label } = parseMintKey(input);
|
|
24
|
+
try {
|
|
25
|
+
return toAdminMintedKey(this.workspaces.mintKey(workspaceId, label));
|
|
26
|
+
}
|
|
27
|
+
catch (error) {
|
|
28
|
+
if (error instanceof Error && error.message.startsWith("Workspace not found:")) {
|
|
29
|
+
throw notFound(error.message);
|
|
30
|
+
}
|
|
31
|
+
throw error;
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
listKeys(workspaceId) {
|
|
35
|
+
if (!this.workspaces.getWorkspace(workspaceId)) {
|
|
36
|
+
throw notFound(`Workspace not found: ${workspaceId}`);
|
|
37
|
+
}
|
|
38
|
+
return this.workspaces.listKeys(workspaceId).map(toAdminKeyMetadata);
|
|
39
|
+
}
|
|
40
|
+
listWorkspaceCredentialMetadata(workspaceId, opts) {
|
|
41
|
+
if (!this.workspaces.getWorkspace(workspaceId)) {
|
|
42
|
+
throw notFound(`Workspace not found: ${workspaceId}`);
|
|
43
|
+
}
|
|
44
|
+
return this.vaults?.listWorkspaceCredentialAdminMetadata(workspaceId, opts)
|
|
45
|
+
?? { data: [], has_more: false, next_page: null };
|
|
46
|
+
}
|
|
47
|
+
revokeKey(keySha256) {
|
|
48
|
+
const existing = this.workspaces.getKey(keySha256);
|
|
49
|
+
if (!existing)
|
|
50
|
+
throw notFound(`Key not found: ${keySha256}`);
|
|
51
|
+
if (existing.revoked_at !== null)
|
|
52
|
+
return toAdminKeyMetadata(existing);
|
|
53
|
+
this.workspaces.revokeKey(keySha256);
|
|
54
|
+
return toAdminKeyMetadata(this.workspaces.getKey(keySha256) ?? existing);
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
function parseCreateWorkspace(input) {
|
|
58
|
+
if (typeof input !== "object" || input === null || Array.isArray(input)) {
|
|
59
|
+
throw invalidRequest("Request body must be a JSON object");
|
|
60
|
+
}
|
|
61
|
+
const { name } = input;
|
|
62
|
+
if (typeof name !== "string" || name.trim().length === 0) {
|
|
63
|
+
throw invalidRequest("`name` must be a non-empty string");
|
|
64
|
+
}
|
|
65
|
+
return { name: name.trim() };
|
|
66
|
+
}
|
|
67
|
+
function parseMintKey(input) {
|
|
68
|
+
if (input === undefined)
|
|
69
|
+
return { label: "default" };
|
|
70
|
+
if (typeof input !== "object" || input === null || Array.isArray(input)) {
|
|
71
|
+
throw invalidRequest("Request body must be a JSON object");
|
|
72
|
+
}
|
|
73
|
+
const { label = "default" } = input;
|
|
74
|
+
if (typeof label !== "string" || label.trim().length === 0) {
|
|
75
|
+
throw invalidRequest("`label` must be a non-empty string");
|
|
76
|
+
}
|
|
77
|
+
return { label: label.trim() };
|
|
78
|
+
}
|
|
79
|
+
function toAdminWorkspace(row) {
|
|
80
|
+
return {
|
|
81
|
+
id: row.workspace_id,
|
|
82
|
+
name: row.name,
|
|
83
|
+
created_at: row.created_at,
|
|
84
|
+
};
|
|
85
|
+
}
|
|
86
|
+
function toAdminMintedKey(row) {
|
|
87
|
+
return {
|
|
88
|
+
workspace_id: row.workspaceId,
|
|
89
|
+
label: row.label,
|
|
90
|
+
key_sha256: row.keySha256,
|
|
91
|
+
api_key: row.plaintextKey,
|
|
92
|
+
};
|
|
93
|
+
}
|
|
94
|
+
function toAdminKeyMetadata(row) {
|
|
95
|
+
return {
|
|
96
|
+
key_sha256: row.key_sha256,
|
|
97
|
+
workspace_id: row.workspace_id,
|
|
98
|
+
label: row.label,
|
|
99
|
+
created_at: row.created_at,
|
|
100
|
+
revoked_at: row.revoked_at,
|
|
101
|
+
};
|
|
102
|
+
}
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
import { overloaded, rateLimited } from "./errors.js";
|
|
2
|
+
export function parseAdmissionLimitsFromEnv(env) {
|
|
3
|
+
return {
|
|
4
|
+
...limitFromEnv("maxActiveSessionsPerWorkspace", "OMA_MAX_ACTIVE_SESSIONS_PER_WORKSPACE", env.OMA_MAX_ACTIVE_SESSIONS_PER_WORKSPACE),
|
|
5
|
+
...limitFromEnv("maxPendingRuntimeTurnsPerWorkspace", "OMA_MAX_PENDING_RUNTIME_TURNS_PER_WORKSPACE", env.OMA_MAX_PENDING_RUNTIME_TURNS_PER_WORKSPACE),
|
|
6
|
+
...limitFromEnv("maxConcurrentUploadsPerWorkspace", "OMA_MAX_CONCURRENT_UPLOADS_PER_WORKSPACE", env.OMA_MAX_CONCURRENT_UPLOADS_PER_WORKSPACE),
|
|
7
|
+
...limitFromEnv("maxConcurrentUploads", "OMA_MAX_CONCURRENT_UPLOADS", env.OMA_MAX_CONCURRENT_UPLOADS),
|
|
8
|
+
...limitFromEnv("maxConcurrentSseStreamsPerWorkspace", "OMA_MAX_CONCURRENT_SSE_STREAMS_PER_WORKSPACE", env.OMA_MAX_CONCURRENT_SSE_STREAMS_PER_WORKSPACE),
|
|
9
|
+
...limitFromEnv("maxConcurrentSseStreams", "OMA_MAX_CONCURRENT_SSE_STREAMS", env.OMA_MAX_CONCURRENT_SSE_STREAMS),
|
|
10
|
+
};
|
|
11
|
+
}
|
|
12
|
+
function limitFromEnv(key, name, raw) {
|
|
13
|
+
if (raw === undefined)
|
|
14
|
+
return {};
|
|
15
|
+
const value = Number(raw.trim());
|
|
16
|
+
if (!Number.isInteger(value) || value <= 0) {
|
|
17
|
+
throw new Error(`${name} must be a positive integer, got ${JSON.stringify(raw)}`);
|
|
18
|
+
}
|
|
19
|
+
return { [key]: value };
|
|
20
|
+
}
|
|
21
|
+
// Tracks in-flight operations whose cost is their lifetime (uploads buffering
|
|
22
|
+
// in RAM, open SSE streams). acquire() throws the wire-shaped rejection;
|
|
23
|
+
// the returned release is idempotent so close/cancel/error paths can all
|
|
24
|
+
// call it safely.
|
|
25
|
+
export class InFlightGauge {
|
|
26
|
+
resource;
|
|
27
|
+
maxPerWorkspace;
|
|
28
|
+
maxTotal;
|
|
29
|
+
onReject;
|
|
30
|
+
perWorkspaceCounts = new Map();
|
|
31
|
+
total = 0;
|
|
32
|
+
constructor(resource, maxPerWorkspace, maxTotal, onReject) {
|
|
33
|
+
this.resource = resource;
|
|
34
|
+
this.maxPerWorkspace = maxPerWorkspace;
|
|
35
|
+
this.maxTotal = maxTotal;
|
|
36
|
+
this.onReject = onReject;
|
|
37
|
+
}
|
|
38
|
+
acquire(workspaceId) {
|
|
39
|
+
const current = this.perWorkspaceCounts.get(workspaceId) ?? 0;
|
|
40
|
+
if (this.maxPerWorkspace !== undefined && current >= this.maxPerWorkspace) {
|
|
41
|
+
this.onReject?.("429");
|
|
42
|
+
throw rateLimited(`Concurrent ${this.resource} limit reached for this workspace; retry later`);
|
|
43
|
+
}
|
|
44
|
+
if (this.maxTotal !== undefined && this.total >= this.maxTotal) {
|
|
45
|
+
this.onReject?.("529");
|
|
46
|
+
throw overloaded();
|
|
47
|
+
}
|
|
48
|
+
this.perWorkspaceCounts.set(workspaceId, current + 1);
|
|
49
|
+
this.total += 1;
|
|
50
|
+
let released = false;
|
|
51
|
+
return () => {
|
|
52
|
+
if (released)
|
|
53
|
+
return;
|
|
54
|
+
released = true;
|
|
55
|
+
const count = this.perWorkspaceCounts.get(workspaceId) ?? 1;
|
|
56
|
+
if (count <= 1)
|
|
57
|
+
this.perWorkspaceCounts.delete(workspaceId);
|
|
58
|
+
else
|
|
59
|
+
this.perWorkspaceCounts.set(workspaceId, count - 1);
|
|
60
|
+
this.total -= 1;
|
|
61
|
+
};
|
|
62
|
+
}
|
|
63
|
+
inFlight(workspaceId) {
|
|
64
|
+
return this.perWorkspaceCounts.get(workspaceId) ?? 0;
|
|
65
|
+
}
|
|
66
|
+
// For the /metrics SSE-streams gauge (0121 C2).
|
|
67
|
+
get totalInFlight() {
|
|
68
|
+
return this.total;
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
export function createAdmissionLimits(config,
|
|
72
|
+
// 0121 C2: telemetry-only; absent hooks change nothing.
|
|
73
|
+
onRejection) {
|
|
74
|
+
return {
|
|
75
|
+
...(config.maxActiveSessionsPerWorkspace === undefined
|
|
76
|
+
? {}
|
|
77
|
+
: { maxActiveSessionsPerWorkspace: config.maxActiveSessionsPerWorkspace }),
|
|
78
|
+
...(config.maxPendingRuntimeTurnsPerWorkspace === undefined
|
|
79
|
+
? {}
|
|
80
|
+
: {
|
|
81
|
+
maxPendingRuntimeTurnsPerWorkspace: config.maxPendingRuntimeTurnsPerWorkspace,
|
|
82
|
+
}),
|
|
83
|
+
uploads: new InFlightGauge("file upload", config.maxConcurrentUploadsPerWorkspace, config.maxConcurrentUploads, onRejection === undefined
|
|
84
|
+
? undefined
|
|
85
|
+
: (status) => onRejection("uploads", status)),
|
|
86
|
+
sseStreams: new InFlightGauge("SSE stream", config.maxConcurrentSseStreamsPerWorkspace, config.maxConcurrentSseStreams, onRejection === undefined
|
|
87
|
+
? undefined
|
|
88
|
+
: (status) => onRejection("streams", status)),
|
|
89
|
+
};
|
|
90
|
+
}
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
import { Hono } from "hono";
|
|
2
|
+
import { invalidRequest } from "../errors.js";
|
|
3
|
+
import { parseJsonBody, parseLimit } from "../http.js";
|
|
4
|
+
import { workspaceIdFrom } from "../workspace.js";
|
|
5
|
+
export function agentsRoutes(service) {
|
|
6
|
+
const app = new Hono();
|
|
7
|
+
app.post("/", async (c) => {
|
|
8
|
+
const body = await parseJsonBody(c.req);
|
|
9
|
+
const agent = service.create(workspaceIdFrom(c), body);
|
|
10
|
+
return c.json(agent, 200);
|
|
11
|
+
});
|
|
12
|
+
app.get("/", (c) => {
|
|
13
|
+
const limit = parseLimit(c.req.query("limit"));
|
|
14
|
+
const page = c.req.query("page") || undefined;
|
|
15
|
+
const includeArchived = parseBoolean(c.req.query("include_archived"));
|
|
16
|
+
return c.json(service.list(workspaceIdFrom(c), {
|
|
17
|
+
...(limit === undefined ? {} : { limit }),
|
|
18
|
+
...(page === undefined ? {} : { page }),
|
|
19
|
+
...(includeArchived === undefined ? {} : { includeArchived }),
|
|
20
|
+
}), 200);
|
|
21
|
+
});
|
|
22
|
+
app.get("/:id/versions", (c) => {
|
|
23
|
+
const limit = parseLimit(c.req.query("limit"));
|
|
24
|
+
const page = c.req.query("page") || undefined;
|
|
25
|
+
return c.json(service.listVersions(workspaceIdFrom(c), c.req.param("id"), {
|
|
26
|
+
...(limit === undefined ? {} : { limit }),
|
|
27
|
+
...(page === undefined ? {} : { page }),
|
|
28
|
+
}), 200);
|
|
29
|
+
});
|
|
30
|
+
app.get("/:id", (c) => {
|
|
31
|
+
const version = parsePositiveInteger(c.req.query("version"), "version");
|
|
32
|
+
return c.json(service.retrieve(workspaceIdFrom(c), c.req.param("id"), version), 200);
|
|
33
|
+
});
|
|
34
|
+
app.post("/:id", async (c) => {
|
|
35
|
+
const body = await parseJsonBody(c.req);
|
|
36
|
+
return c.json(service.update(workspaceIdFrom(c), c.req.param("id"), body), 200);
|
|
37
|
+
});
|
|
38
|
+
app.post("/:id/archive", (c) => {
|
|
39
|
+
return c.json(service.archive(workspaceIdFrom(c), c.req.param("id")), 200);
|
|
40
|
+
});
|
|
41
|
+
return app;
|
|
42
|
+
}
|
|
43
|
+
function parsePositiveInteger(value, field) {
|
|
44
|
+
if (value === undefined)
|
|
45
|
+
return undefined;
|
|
46
|
+
if (!/^[1-9][0-9]*$/.test(value)) {
|
|
47
|
+
throw invalidRequest(`\`${field}\` must be a positive integer`);
|
|
48
|
+
}
|
|
49
|
+
const parsed = Number(value);
|
|
50
|
+
if (!Number.isSafeInteger(parsed)) {
|
|
51
|
+
throw invalidRequest(`\`${field}\` must be a positive integer`);
|
|
52
|
+
}
|
|
53
|
+
return parsed;
|
|
54
|
+
}
|
|
55
|
+
function parseBoolean(value) {
|
|
56
|
+
if (value === undefined)
|
|
57
|
+
return undefined;
|
|
58
|
+
if (value === "true")
|
|
59
|
+
return true;
|
|
60
|
+
if (value === "false")
|
|
61
|
+
return false;
|
|
62
|
+
throw invalidRequest("`include_archived` must be `true` or `false`");
|
|
63
|
+
}
|