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,100 @@
|
|
|
1
|
+
export function translatePiEvent(input, context = {}) {
|
|
2
|
+
if (!isObject(input))
|
|
3
|
+
return [];
|
|
4
|
+
const type = typeof input.type === "string" ? input.type : "";
|
|
5
|
+
if (type === "agent_start") {
|
|
6
|
+
return [{ type: "session.status_running", payload: {} }];
|
|
7
|
+
}
|
|
8
|
+
if (type === "message_end") {
|
|
9
|
+
return translateMessageEnd(input, context);
|
|
10
|
+
}
|
|
11
|
+
if (type === "tool_execution_end") {
|
|
12
|
+
return translateToolExecutionEnd(input, context);
|
|
13
|
+
}
|
|
14
|
+
if (type === "agent_end") {
|
|
15
|
+
return translateAgentEnd(input);
|
|
16
|
+
}
|
|
17
|
+
return [];
|
|
18
|
+
}
|
|
19
|
+
function translateMessageEnd(event, context) {
|
|
20
|
+
if (!isObject(event.message))
|
|
21
|
+
return [];
|
|
22
|
+
const message = event.message;
|
|
23
|
+
const role = typeof message.role === "string" ? message.role : "";
|
|
24
|
+
if (role !== "assistant")
|
|
25
|
+
return [];
|
|
26
|
+
const drafts = [];
|
|
27
|
+
const blocks = Array.isArray(message.content) ? message.content : [];
|
|
28
|
+
const textBlocks = blocks
|
|
29
|
+
.filter(isObject)
|
|
30
|
+
.filter((b) => b.type === "text" && typeof b.text === "string")
|
|
31
|
+
.map((b) => ({ type: "text", text: b.text }));
|
|
32
|
+
if (textBlocks.length > 0) {
|
|
33
|
+
drafts.push({
|
|
34
|
+
type: "agent.message",
|
|
35
|
+
payload: { content: textBlocks },
|
|
36
|
+
});
|
|
37
|
+
}
|
|
38
|
+
for (const block of blocks) {
|
|
39
|
+
if (!isObject(block) || block.type !== "toolCall")
|
|
40
|
+
continue;
|
|
41
|
+
const toolUseId = typeof block.id === "string" ? block.id : undefined;
|
|
42
|
+
const name = typeof block.name === "string" ? block.name : undefined;
|
|
43
|
+
const args = isObject(block.arguments) ? block.arguments : {};
|
|
44
|
+
if (!toolUseId || !name)
|
|
45
|
+
continue;
|
|
46
|
+
if (context.customToolNames?.has(name))
|
|
47
|
+
continue;
|
|
48
|
+
if (context.suppressPiToolUse?.(toolUseId) === true)
|
|
49
|
+
continue;
|
|
50
|
+
// ADR 0011: event IDs stay server-assigned sevt_*; Pi's toolu_* remains
|
|
51
|
+
// payload correlation data and inbound handling translates as needed.
|
|
52
|
+
const payload = {
|
|
53
|
+
tool_use_id: toolUseId,
|
|
54
|
+
name,
|
|
55
|
+
input: args,
|
|
56
|
+
};
|
|
57
|
+
drafts.push({ type: "agent.tool_use", payload });
|
|
58
|
+
}
|
|
59
|
+
return drafts;
|
|
60
|
+
}
|
|
61
|
+
function translateToolExecutionEnd(event, context) {
|
|
62
|
+
const toolName = typeof event.toolName === "string" && event.toolName.length > 0
|
|
63
|
+
? event.toolName
|
|
64
|
+
: undefined;
|
|
65
|
+
if (toolName && context.customToolNames?.has(toolName))
|
|
66
|
+
return [];
|
|
67
|
+
const toolUseId = typeof event.toolCallId === "string" && event.toolCallId.length > 0
|
|
68
|
+
? event.toolCallId
|
|
69
|
+
: undefined;
|
|
70
|
+
if (!toolUseId)
|
|
71
|
+
return [];
|
|
72
|
+
const payload = {
|
|
73
|
+
tool_use_id: context.publicToolUseIdForPiToolCallId?.(toolUseId) ?? toolUseId,
|
|
74
|
+
content: [],
|
|
75
|
+
is_error: event.isError === true,
|
|
76
|
+
};
|
|
77
|
+
if (isObject(event.result)) {
|
|
78
|
+
if (Array.isArray(event.result.content)) {
|
|
79
|
+
payload.content = event.result.content;
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
return [{ type: "agent.tool_result", payload }];
|
|
83
|
+
}
|
|
84
|
+
function translateAgentEnd(event) {
|
|
85
|
+
// Provisional mapping: C.0 fixtures only observed willRetry=false.
|
|
86
|
+
// Keep this branch explicit (and easy to delete) until a retry trajectory
|
|
87
|
+
// is captured and the event shape is confirmed in fixtures.
|
|
88
|
+
if (event.willRetry === true) {
|
|
89
|
+
return [{ type: "session.status_rescheduled", payload: {} }];
|
|
90
|
+
}
|
|
91
|
+
return [
|
|
92
|
+
{
|
|
93
|
+
type: "session.status_idle",
|
|
94
|
+
payload: { stop_reason: { type: "end_turn" } },
|
|
95
|
+
},
|
|
96
|
+
];
|
|
97
|
+
}
|
|
98
|
+
function isObject(value) {
|
|
99
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
100
|
+
}
|
|
@@ -0,0 +1,164 @@
|
|
|
1
|
+
import { isJsonObject } from "../../types/json.js";
|
|
2
|
+
import { invalidRequest } from "../errors.js";
|
|
3
|
+
const MAX_SESSION_VAULT_IDS = 20;
|
|
4
|
+
export function parseCreateSession(input) {
|
|
5
|
+
const obj = objectInput(input);
|
|
6
|
+
rejectUnsupportedField(obj, "sandbox");
|
|
7
|
+
rejectUnsupportedField(obj, "sandbox_provider");
|
|
8
|
+
rejectUnsupportedField(obj, "sandboxProviderSelection");
|
|
9
|
+
rejectUnsupportedField(obj, "sandboxProviderSelectionOptions");
|
|
10
|
+
rejectUnsupportedField(obj, "sandboxProviderFactory");
|
|
11
|
+
rejectUnknownFields(obj, [
|
|
12
|
+
"agent",
|
|
13
|
+
"environment_id",
|
|
14
|
+
"vault_ids",
|
|
15
|
+
"title",
|
|
16
|
+
"metadata",
|
|
17
|
+
"resources",
|
|
18
|
+
]);
|
|
19
|
+
return {
|
|
20
|
+
agent: agentField(obj),
|
|
21
|
+
environment_id: stringField(obj, "environment_id", { required: true }),
|
|
22
|
+
vault_ids: vaultIdsField(obj),
|
|
23
|
+
title: nullableStringField(obj, "title") ?? undefined,
|
|
24
|
+
metadata: metadataField(obj) ?? undefined,
|
|
25
|
+
resources: resourcesField(obj),
|
|
26
|
+
};
|
|
27
|
+
}
|
|
28
|
+
function vaultIdsField(obj) {
|
|
29
|
+
const value = obj.vault_ids;
|
|
30
|
+
if (value === undefined)
|
|
31
|
+
return undefined;
|
|
32
|
+
if (!Array.isArray(value)) {
|
|
33
|
+
throw invalidRequest("`vault_ids` must be an array of vault ids");
|
|
34
|
+
}
|
|
35
|
+
if (value.length > MAX_SESSION_VAULT_IDS) {
|
|
36
|
+
throw invalidRequest(`\`vault_ids\` must contain at most ${MAX_SESSION_VAULT_IDS} vaults`);
|
|
37
|
+
}
|
|
38
|
+
return value.map((item) => {
|
|
39
|
+
if (typeof item !== "string" || !item.startsWith("vlt_")) {
|
|
40
|
+
throw invalidRequest("`vault_ids` entries must be vault ids");
|
|
41
|
+
}
|
|
42
|
+
return item;
|
|
43
|
+
});
|
|
44
|
+
}
|
|
45
|
+
export function parseAgentRef(agent) {
|
|
46
|
+
if (typeof agent === "string")
|
|
47
|
+
return { id: agent };
|
|
48
|
+
return agent.version === undefined
|
|
49
|
+
? { id: agent.id }
|
|
50
|
+
: { id: agent.id, version: agent.version };
|
|
51
|
+
}
|
|
52
|
+
function rejectUnsupportedField(obj, field) {
|
|
53
|
+
if (obj[field] !== undefined) {
|
|
54
|
+
throw invalidRequest(`Field \`${field}\` is not yet supported by this server.`);
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
function rejectUnknownFields(obj, allowedFields) {
|
|
58
|
+
const allowed = new Set(allowedFields);
|
|
59
|
+
for (const field of Object.keys(obj)) {
|
|
60
|
+
if (allowed.has(field))
|
|
61
|
+
continue;
|
|
62
|
+
throw invalidRequest(`Unsupported session create field: \`${field}\`.`);
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
function objectInput(input) {
|
|
66
|
+
if (!isJsonObject(input)) {
|
|
67
|
+
throw invalidRequest("Request body must be a JSON object");
|
|
68
|
+
}
|
|
69
|
+
return input;
|
|
70
|
+
}
|
|
71
|
+
function agentField(obj) {
|
|
72
|
+
const value = obj.agent;
|
|
73
|
+
if (typeof value === "string" && value.length > 0)
|
|
74
|
+
return value;
|
|
75
|
+
if (isJsonObject(value)) {
|
|
76
|
+
const type = stringField(value, "type", { required: true });
|
|
77
|
+
if (type !== "agent") {
|
|
78
|
+
throw invalidRequest("`agent.type` must be `agent`");
|
|
79
|
+
}
|
|
80
|
+
const version = value.version;
|
|
81
|
+
if (version !== undefined) {
|
|
82
|
+
if (typeof version !== "number" ||
|
|
83
|
+
!Number.isSafeInteger(version) ||
|
|
84
|
+
version <= 0) {
|
|
85
|
+
throw invalidRequest("`agent.version` must be a positive integer");
|
|
86
|
+
}
|
|
87
|
+
return {
|
|
88
|
+
type,
|
|
89
|
+
id: stringField(value, "id", { required: true }),
|
|
90
|
+
version,
|
|
91
|
+
};
|
|
92
|
+
}
|
|
93
|
+
return {
|
|
94
|
+
type,
|
|
95
|
+
id: stringField(value, "id", { required: true }),
|
|
96
|
+
};
|
|
97
|
+
}
|
|
98
|
+
throw invalidRequest("`agent` must be a non-empty string or agent object");
|
|
99
|
+
}
|
|
100
|
+
function stringField(obj, field, opts = {}) {
|
|
101
|
+
const value = obj[field];
|
|
102
|
+
if (typeof value === "string" && value.length > 0)
|
|
103
|
+
return value;
|
|
104
|
+
if (value === undefined && opts.required !== true)
|
|
105
|
+
return "";
|
|
106
|
+
throw invalidRequest(`\`${field}\` must be a non-empty string`);
|
|
107
|
+
}
|
|
108
|
+
function nullableStringField(obj, field) {
|
|
109
|
+
const value = obj[field];
|
|
110
|
+
if (value === undefined)
|
|
111
|
+
return undefined;
|
|
112
|
+
if (value === null)
|
|
113
|
+
return null;
|
|
114
|
+
if (typeof value === "string")
|
|
115
|
+
return value;
|
|
116
|
+
throw invalidRequest(`\`${field}\` must be a string or null`);
|
|
117
|
+
}
|
|
118
|
+
function metadataField(obj) {
|
|
119
|
+
const value = obj.metadata;
|
|
120
|
+
if (value === undefined)
|
|
121
|
+
return undefined;
|
|
122
|
+
if (!isJsonObject(value)) {
|
|
123
|
+
throw invalidRequest("`metadata` must be an object");
|
|
124
|
+
}
|
|
125
|
+
const metadata = {};
|
|
126
|
+
for (const [k, v] of Object.entries(value)) {
|
|
127
|
+
if (typeof v !== "string") {
|
|
128
|
+
throw invalidRequest("`metadata` values must be strings");
|
|
129
|
+
}
|
|
130
|
+
metadata[k] = v;
|
|
131
|
+
}
|
|
132
|
+
return metadata;
|
|
133
|
+
}
|
|
134
|
+
function resourcesField(obj) {
|
|
135
|
+
const value = obj.resources;
|
|
136
|
+
if (value === undefined)
|
|
137
|
+
return undefined;
|
|
138
|
+
if (!Array.isArray(value)) {
|
|
139
|
+
throw invalidRequest("`resources` must be an array");
|
|
140
|
+
}
|
|
141
|
+
return value.map((entry, index) => resourceField(entry, index));
|
|
142
|
+
}
|
|
143
|
+
function resourceField(value, index) {
|
|
144
|
+
if (!isJsonObject(value)) {
|
|
145
|
+
throw invalidRequest(`\`resources[${index}]\` must be an object`);
|
|
146
|
+
}
|
|
147
|
+
const type = stringField(value, "type", { required: true });
|
|
148
|
+
if (type !== "file") {
|
|
149
|
+
throw invalidRequest(`Unsupported session resource type: ${type}.`);
|
|
150
|
+
}
|
|
151
|
+
rejectUnknownFields(value, ["type", "file_id", "mount_path"]);
|
|
152
|
+
const mountPath = nullableStringField(value, "mount_path");
|
|
153
|
+
return {
|
|
154
|
+
type,
|
|
155
|
+
file_id: resourceStringField(value, "file_id", index),
|
|
156
|
+
...(mountPath === undefined || mountPath === null ? {} : { mount_path: mountPath }),
|
|
157
|
+
};
|
|
158
|
+
}
|
|
159
|
+
function resourceStringField(obj, field, index) {
|
|
160
|
+
const value = obj[field];
|
|
161
|
+
if (typeof value === "string" && value.length > 0)
|
|
162
|
+
return value;
|
|
163
|
+
throw invalidRequest(`\`resources[${index}].${field}\` must be a non-empty string`);
|
|
164
|
+
}
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
import { invalidRequest } from "../errors.js";
|
|
2
|
+
const SESSION_UPLOADS_ROOT = "/mnt/session/uploads";
|
|
3
|
+
const SEGMENT_PATTERN = /^[A-Za-z0-9._-]+$/;
|
|
4
|
+
const MAX_SEGMENT_LENGTH = 255;
|
|
5
|
+
const MAX_CANONICAL_MOUNT_PATH_LENGTH = 1024;
|
|
6
|
+
export function normalizeSessionMountPath(mountPath, fileId) {
|
|
7
|
+
const raw = mountPath ?? fileId;
|
|
8
|
+
if (raw.includes("\0")) {
|
|
9
|
+
throw invalidRequest("Invalid file resource: mount_path must not contain NUL bytes");
|
|
10
|
+
}
|
|
11
|
+
if (raw.includes("\\")) {
|
|
12
|
+
throw invalidRequest("Invalid file resource: mount_path must not contain backslashes");
|
|
13
|
+
}
|
|
14
|
+
const relative = raw.replace(/^\/+/, "");
|
|
15
|
+
if (relative.length === 0) {
|
|
16
|
+
throw invalidRequest("Invalid file resource: mount_path must not be empty");
|
|
17
|
+
}
|
|
18
|
+
const segments = relative.split("/");
|
|
19
|
+
for (const segment of segments) {
|
|
20
|
+
validateSegment(segment);
|
|
21
|
+
}
|
|
22
|
+
const canonical = `${SESSION_UPLOADS_ROOT}/${segments.join("/")}`;
|
|
23
|
+
if (canonical.length > MAX_CANONICAL_MOUNT_PATH_LENGTH) {
|
|
24
|
+
throw invalidRequest("Invalid file resource: mount_path exceeds 1024 characters");
|
|
25
|
+
}
|
|
26
|
+
return { mountPath: canonical, segments };
|
|
27
|
+
}
|
|
28
|
+
export function normalizeSessionFileResources(resources) {
|
|
29
|
+
const normalized = resources.map((resource) => ({
|
|
30
|
+
fileId: resource.fileId,
|
|
31
|
+
...normalizeSessionMountPath(resource.mountPath, resource.fileId),
|
|
32
|
+
}));
|
|
33
|
+
for (let i = 0; i < normalized.length; i += 1) {
|
|
34
|
+
for (let j = i + 1; j < normalized.length; j += 1) {
|
|
35
|
+
const left = normalized[i];
|
|
36
|
+
const right = normalized[j];
|
|
37
|
+
if (left.mountPath === right.mountPath) {
|
|
38
|
+
throw invalidRequest("Duplicate file resource mount_path");
|
|
39
|
+
}
|
|
40
|
+
if (isPrefix(left.segments, right.segments) || isPrefix(right.segments, left.segments)) {
|
|
41
|
+
throw invalidRequest("Overlapping file resource mount_path");
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
return normalized;
|
|
46
|
+
}
|
|
47
|
+
function validateSegment(segment) {
|
|
48
|
+
if (segment.length === 0) {
|
|
49
|
+
throw invalidRequest("Invalid file resource: mount_path segment must not be empty");
|
|
50
|
+
}
|
|
51
|
+
if (segment === "." || segment === "..") {
|
|
52
|
+
throw invalidRequest("Invalid file resource: mount_path must not contain . or .. segments");
|
|
53
|
+
}
|
|
54
|
+
if (segment.length > MAX_SEGMENT_LENGTH) {
|
|
55
|
+
throw invalidRequest("Invalid file resource: mount_path segment exceeds 255 characters");
|
|
56
|
+
}
|
|
57
|
+
if (!SEGMENT_PATTERN.test(segment)) {
|
|
58
|
+
throw invalidRequest("Invalid file resource: mount_path contains unsupported characters");
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
function isPrefix(left, right) {
|
|
62
|
+
return left.length < right.length && left.every((segment, index) => segment === right[index]);
|
|
63
|
+
}
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
import { Hono } from "hono";
|
|
2
|
+
import { parseJsonBody, parseLimit, parseOrder } from "../http.js";
|
|
3
|
+
import { invalidRequest } from "../errors.js";
|
|
4
|
+
import { requestFingerprint, validateIdempotencyKey, } from "../request-idempotency.js";
|
|
5
|
+
import { workspaceIdFrom } from "../workspace.js";
|
|
6
|
+
import { toManagedSession } from "./serialize.js";
|
|
7
|
+
export function sessionsRoutes(service, events) {
|
|
8
|
+
const app = new Hono();
|
|
9
|
+
app.post("/", async (c) => {
|
|
10
|
+
const idempotencyKey = c.req.header("idempotency-key");
|
|
11
|
+
if (idempotencyKey !== undefined) {
|
|
12
|
+
const rawBody = new Uint8Array(await c.req.raw.arrayBuffer());
|
|
13
|
+
const body = parseJsonBytes(rawBody);
|
|
14
|
+
const method = c.req.method.toUpperCase();
|
|
15
|
+
const concretePath = new URL(c.req.url).pathname;
|
|
16
|
+
const response = await service.createIdempotent(workspaceIdFrom(c), body, {
|
|
17
|
+
method,
|
|
18
|
+
concretePath,
|
|
19
|
+
key: validateIdempotencyKey(idempotencyKey),
|
|
20
|
+
routeLabel: "POST /v1/sessions",
|
|
21
|
+
fingerprintSha256: requestFingerprint(method, concretePath, rawBody),
|
|
22
|
+
}, { requestId: c.get("requestId") });
|
|
23
|
+
return jsonResponse(response.body, response.status, c.get("requestId"), response.headers);
|
|
24
|
+
}
|
|
25
|
+
const body = await parseJsonBody(c.req);
|
|
26
|
+
const session = await service.create(workspaceIdFrom(c), body);
|
|
27
|
+
return c.json(session, 200);
|
|
28
|
+
});
|
|
29
|
+
app.get("/", (c) => {
|
|
30
|
+
const limit = parseLimit(c.req.query("limit"));
|
|
31
|
+
const page = c.req.query("page") || undefined;
|
|
32
|
+
const order = parseOrder(c.req.query("order"));
|
|
33
|
+
const agentId = c.req.query("agent_id") || undefined;
|
|
34
|
+
const includeArchived = parseBoolean(c.req.query("include_archived"));
|
|
35
|
+
return c.json(service.list(workspaceIdFrom(c), {
|
|
36
|
+
...(limit === undefined ? {} : { limit }),
|
|
37
|
+
...(page === undefined ? {} : { page }),
|
|
38
|
+
...(order === undefined ? {} : { order }),
|
|
39
|
+
...(agentId === undefined ? {} : { agentId }),
|
|
40
|
+
...(includeArchived === undefined ? {} : { includeArchived }),
|
|
41
|
+
}), 200);
|
|
42
|
+
});
|
|
43
|
+
app.get("/:id", (c) => {
|
|
44
|
+
return c.json(service.retrieve(workspaceIdFrom(c), c.req.param("id")), 200);
|
|
45
|
+
});
|
|
46
|
+
app.post("/:id/archive", async (c) => {
|
|
47
|
+
const sessionId = c.req.param("id");
|
|
48
|
+
const row = events.archiveSessionRowAfterPreflight(workspaceIdFrom(c), sessionId);
|
|
49
|
+
await events.archiveSession(workspaceIdFrom(c), sessionId);
|
|
50
|
+
return c.json(toManagedSession(row), 200);
|
|
51
|
+
});
|
|
52
|
+
app.delete("/:id", async (c) => {
|
|
53
|
+
const sessionId = c.req.param("id");
|
|
54
|
+
// The running-session guard lives in service.delete (domain-owned), so it
|
|
55
|
+
// runs before any mutation regardless of caller; the route stays thin.
|
|
56
|
+
const deleted = await service.delete(workspaceIdFrom(c), sessionId);
|
|
57
|
+
await events.deleteSession(workspaceIdFrom(c), sessionId);
|
|
58
|
+
return c.json(deleted, 200);
|
|
59
|
+
});
|
|
60
|
+
return app;
|
|
61
|
+
}
|
|
62
|
+
function parseJsonBytes(bytes) {
|
|
63
|
+
try {
|
|
64
|
+
return JSON.parse(new TextDecoder().decode(bytes));
|
|
65
|
+
}
|
|
66
|
+
catch (error) {
|
|
67
|
+
throw invalidRequest("Request body must be valid JSON", String(error));
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
function jsonResponse(body, status, requestId, extraHeaders = {}) {
|
|
71
|
+
return new Response(JSON.stringify(body), {
|
|
72
|
+
status,
|
|
73
|
+
headers: {
|
|
74
|
+
"content-type": "application/json",
|
|
75
|
+
"request-id": requestId,
|
|
76
|
+
...extraHeaders,
|
|
77
|
+
},
|
|
78
|
+
});
|
|
79
|
+
}
|
|
80
|
+
function parseBoolean(value) {
|
|
81
|
+
if (value === undefined)
|
|
82
|
+
return undefined;
|
|
83
|
+
if (value === "true")
|
|
84
|
+
return true;
|
|
85
|
+
if (value === "false")
|
|
86
|
+
return false;
|
|
87
|
+
throw invalidRequest("`include_archived` must be `true` or `false`");
|
|
88
|
+
}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
export function toManagedSession(row) {
|
|
2
|
+
return {
|
|
3
|
+
id: row.id,
|
|
4
|
+
type: row.type,
|
|
5
|
+
agent: row.agent,
|
|
6
|
+
environment_id: row.environment_id,
|
|
7
|
+
vault_ids: [...(row.vault_ids ?? [])],
|
|
8
|
+
status: row.status,
|
|
9
|
+
title: row.title,
|
|
10
|
+
metadata: row.metadata,
|
|
11
|
+
created_at: row.created_at,
|
|
12
|
+
updated_at: row.updated_at,
|
|
13
|
+
archived_at: row.archived_at,
|
|
14
|
+
usage: row.usage,
|
|
15
|
+
resources: row.resources.map((resource) => ({
|
|
16
|
+
id: resource.id,
|
|
17
|
+
type: resource.type,
|
|
18
|
+
file_id: resource.file_id,
|
|
19
|
+
mount_path: resource.mount_path,
|
|
20
|
+
created_at: resource.created_at,
|
|
21
|
+
updated_at: resource.updated_at,
|
|
22
|
+
})),
|
|
23
|
+
};
|
|
24
|
+
}
|