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,168 @@
|
|
|
1
|
+
import { createHostPassthroughSandboxProvider, } from "./provider.js";
|
|
2
|
+
import { createDockerSandboxProviderFactory, } from "./docker.js";
|
|
3
|
+
import { createMicrosandboxSandboxProviderFactory } from "./microsandbox.js";
|
|
4
|
+
export function parseSandboxProviderSelection(input) {
|
|
5
|
+
const obj = objectInput(input, "sandbox provider selection");
|
|
6
|
+
const type = stringField(obj, "type");
|
|
7
|
+
switch (type) {
|
|
8
|
+
case "none":
|
|
9
|
+
rejectUnknownFields(obj, ["type"]);
|
|
10
|
+
return { type };
|
|
11
|
+
case "host-passthrough": {
|
|
12
|
+
rejectUnknownFields(obj, [
|
|
13
|
+
"type",
|
|
14
|
+
"unsafeAllowHostPassthrough",
|
|
15
|
+
"envAllowlist",
|
|
16
|
+
]);
|
|
17
|
+
const unsafeAllowHostPassthrough = obj.unsafeAllowHostPassthrough === true;
|
|
18
|
+
if (!unsafeAllowHostPassthrough) {
|
|
19
|
+
throw new Error("`unsafeAllowHostPassthrough` must be true for host-passthrough");
|
|
20
|
+
}
|
|
21
|
+
return {
|
|
22
|
+
type,
|
|
23
|
+
unsafeAllowHostPassthrough,
|
|
24
|
+
...optionalEnvAllowlist(obj),
|
|
25
|
+
};
|
|
26
|
+
}
|
|
27
|
+
case "docker-local": {
|
|
28
|
+
rejectUnknownFields(obj, [
|
|
29
|
+
"type",
|
|
30
|
+
"envAllowlist",
|
|
31
|
+
"operationTimeoutMs",
|
|
32
|
+
"reapStaleContainersOlderThanMs",
|
|
33
|
+
"unsafeAllowHostPassthrough",
|
|
34
|
+
]);
|
|
35
|
+
if (obj.unsafeAllowHostPassthrough !== undefined) {
|
|
36
|
+
throw new Error("`unsafeAllowHostPassthrough` is only valid for host-passthrough");
|
|
37
|
+
}
|
|
38
|
+
return {
|
|
39
|
+
type,
|
|
40
|
+
...optionalEnvAllowlist(obj),
|
|
41
|
+
...optionalOperationTimeoutMs(obj),
|
|
42
|
+
...optionalReapStaleContainersOlderThanMs(obj),
|
|
43
|
+
};
|
|
44
|
+
}
|
|
45
|
+
case "microsandbox-local": {
|
|
46
|
+
rejectUnknownFields(obj, [
|
|
47
|
+
"type",
|
|
48
|
+
"operationTimeoutMs",
|
|
49
|
+
"reapStaleSandboxesOlderThanMs",
|
|
50
|
+
]);
|
|
51
|
+
return {
|
|
52
|
+
type,
|
|
53
|
+
...optionalOperationTimeoutMs(obj),
|
|
54
|
+
...optionalReapStaleSandboxesOlderThanMs(obj),
|
|
55
|
+
};
|
|
56
|
+
}
|
|
57
|
+
default:
|
|
58
|
+
throw new Error(`Unsupported sandbox provider type: ${type}`);
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
export function resolveSandboxProviderFactory(selection, opts = {}) {
|
|
62
|
+
if (selection === undefined || selection.type === "none") {
|
|
63
|
+
rejectEgressOption(opts, selection?.type ?? "none");
|
|
64
|
+
return undefined;
|
|
65
|
+
}
|
|
66
|
+
if (selection.type === "host-passthrough") {
|
|
67
|
+
rejectEgressOption(opts, selection.type);
|
|
68
|
+
if (selection.unsafeAllowHostPassthrough !== true) {
|
|
69
|
+
throw new Error("`unsafeAllowHostPassthrough` must be true for host-passthrough");
|
|
70
|
+
}
|
|
71
|
+
if (opts.allowUnsafeHostPassthrough !== true) {
|
|
72
|
+
throw new Error("Host passthrough provider is disabled by deployment configuration");
|
|
73
|
+
}
|
|
74
|
+
if (!opts.hostPassthroughWorkspaceRoot) {
|
|
75
|
+
throw new Error("Host passthrough provider requires a deployment workspace root");
|
|
76
|
+
}
|
|
77
|
+
const workspaceRoot = opts.hostPassthroughWorkspaceRoot;
|
|
78
|
+
return async () => createHostPassthroughSandboxProvider({
|
|
79
|
+
workspaceRoot,
|
|
80
|
+
unsafeAllowHostPassthrough: selection.unsafeAllowHostPassthrough,
|
|
81
|
+
envAllowlist: selection.envAllowlist,
|
|
82
|
+
});
|
|
83
|
+
}
|
|
84
|
+
if (selection.type === "docker-local") {
|
|
85
|
+
if (opts.allowDockerLocal !== true) {
|
|
86
|
+
throw new Error("Docker-local sandbox provider is disabled by deployment configuration");
|
|
87
|
+
}
|
|
88
|
+
return createDockerSandboxProviderFactory({
|
|
89
|
+
envAllowlist: selection.envAllowlist,
|
|
90
|
+
operationTimeoutMs: selection.operationTimeoutMs,
|
|
91
|
+
reapStaleContainersOlderThanMs: selection.reapStaleContainersOlderThanMs,
|
|
92
|
+
...(opts.egress === undefined ? {} : { egress: opts.egress }),
|
|
93
|
+
...(opts.onReaped === undefined ? {} : { onReaped: opts.onReaped }),
|
|
94
|
+
});
|
|
95
|
+
}
|
|
96
|
+
if (selection.type === "microsandbox-local") {
|
|
97
|
+
rejectEgressOption(opts, selection.type);
|
|
98
|
+
if (opts.allowMicrosandboxLocal !== true) {
|
|
99
|
+
throw new Error("Microsandbox-local sandbox provider is disabled by deployment configuration");
|
|
100
|
+
}
|
|
101
|
+
return createMicrosandboxSandboxProviderFactory({
|
|
102
|
+
operationTimeoutMs: selection.operationTimeoutMs,
|
|
103
|
+
reapStaleSandboxesOlderThanMs: selection.reapStaleSandboxesOlderThanMs,
|
|
104
|
+
...(opts.onReaped === undefined ? {} : { onReaped: opts.onReaped }),
|
|
105
|
+
});
|
|
106
|
+
}
|
|
107
|
+
const _exhaustive = selection;
|
|
108
|
+
throw new Error(`Unsupported sandbox provider type: ${String(_exhaustive)}`);
|
|
109
|
+
}
|
|
110
|
+
function rejectEgressOption(opts, providerType) {
|
|
111
|
+
if (opts.egress !== undefined) {
|
|
112
|
+
throw new Error(`egress is only supported by docker-local, not ${providerType}`);
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
function objectInput(input, label) {
|
|
116
|
+
if (typeof input !== "object" || input === null || Array.isArray(input)) {
|
|
117
|
+
throw new Error(`${label} must be a JSON object`);
|
|
118
|
+
}
|
|
119
|
+
return input;
|
|
120
|
+
}
|
|
121
|
+
function stringField(obj, field) {
|
|
122
|
+
const value = obj[field];
|
|
123
|
+
if (typeof value === "string" && value.length > 0)
|
|
124
|
+
return value;
|
|
125
|
+
throw new Error(`\`${field}\` must be a non-empty string`);
|
|
126
|
+
}
|
|
127
|
+
function optionalEnvAllowlist(obj) {
|
|
128
|
+
if (obj.envAllowlist === undefined)
|
|
129
|
+
return {};
|
|
130
|
+
if (!Array.isArray(obj.envAllowlist)) {
|
|
131
|
+
throw new Error("`envAllowlist` must be an array of strings");
|
|
132
|
+
}
|
|
133
|
+
const envAllowlist = obj.envAllowlist.map((value) => {
|
|
134
|
+
if (typeof value !== "string" || value.length === 0) {
|
|
135
|
+
throw new Error("`envAllowlist` must be an array of non-empty strings");
|
|
136
|
+
}
|
|
137
|
+
return value;
|
|
138
|
+
});
|
|
139
|
+
return { envAllowlist };
|
|
140
|
+
}
|
|
141
|
+
function optionalOperationTimeoutMs(obj) {
|
|
142
|
+
return optionalPositiveIntegerField(obj, "operationTimeoutMs");
|
|
143
|
+
}
|
|
144
|
+
function optionalReapStaleContainersOlderThanMs(obj) {
|
|
145
|
+
return optionalPositiveIntegerField(obj, "reapStaleContainersOlderThanMs");
|
|
146
|
+
}
|
|
147
|
+
function optionalReapStaleSandboxesOlderThanMs(obj) {
|
|
148
|
+
return optionalPositiveIntegerField(obj, "reapStaleSandboxesOlderThanMs");
|
|
149
|
+
}
|
|
150
|
+
function optionalPositiveIntegerField(obj, field) {
|
|
151
|
+
if (obj[field] === undefined)
|
|
152
|
+
return {};
|
|
153
|
+
const value = obj[field];
|
|
154
|
+
if (typeof value !== "number" ||
|
|
155
|
+
!Number.isSafeInteger(value) ||
|
|
156
|
+
value <= 0) {
|
|
157
|
+
throw new Error(`\`${field}\` must be a positive integer`);
|
|
158
|
+
}
|
|
159
|
+
return { [field]: value };
|
|
160
|
+
}
|
|
161
|
+
function rejectUnknownFields(obj, allowedFields) {
|
|
162
|
+
const allowed = new Set(allowedFields);
|
|
163
|
+
for (const field of Object.keys(obj)) {
|
|
164
|
+
if (allowed.has(field))
|
|
165
|
+
continue;
|
|
166
|
+
throw new Error(`Unsupported sandbox provider field: ${field}`);
|
|
167
|
+
}
|
|
168
|
+
}
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
export const zeroModelUsage = {
|
|
2
|
+
cache_creation_input_tokens: 0,
|
|
3
|
+
cache_read_input_tokens: 0,
|
|
4
|
+
input_tokens: 0,
|
|
5
|
+
output_tokens: 0,
|
|
6
|
+
speed: null,
|
|
7
|
+
};
|
|
8
|
+
export function spanModelRequestStartDraft(input) {
|
|
9
|
+
const message = assistantMessage(input);
|
|
10
|
+
if (!message || eventType(input) !== "message_start")
|
|
11
|
+
return [];
|
|
12
|
+
if (!hasModelRequestMetadata(message))
|
|
13
|
+
return [];
|
|
14
|
+
return [{ type: "span.model_request_start", payload: {} }];
|
|
15
|
+
}
|
|
16
|
+
export function spanModelRequestEndDraft(input, startEventId) {
|
|
17
|
+
const message = assistantMessage(input);
|
|
18
|
+
if (!message || eventType(input) !== "message_end")
|
|
19
|
+
return [];
|
|
20
|
+
if (!startEventId)
|
|
21
|
+
return [];
|
|
22
|
+
const payload = {
|
|
23
|
+
model_request_start_id: startEventId,
|
|
24
|
+
is_error: message.stopReason === "aborted" || message.stopReason === "error",
|
|
25
|
+
model_usage: usageFromMessage(message),
|
|
26
|
+
};
|
|
27
|
+
return [{ type: "span.model_request_end", payload: toJsonObject(payload) }];
|
|
28
|
+
}
|
|
29
|
+
export function syntheticSpanModelRequestEndDrafts(startEventIds) {
|
|
30
|
+
return startEventIds.map((startEventId) => ({
|
|
31
|
+
type: "span.model_request_end",
|
|
32
|
+
payload: toJsonObject({
|
|
33
|
+
model_request_start_id: startEventId,
|
|
34
|
+
is_error: true,
|
|
35
|
+
model_usage: zeroModelUsage,
|
|
36
|
+
}),
|
|
37
|
+
}));
|
|
38
|
+
}
|
|
39
|
+
function assistantMessage(input) {
|
|
40
|
+
if (!isObject(input) || !isObject(input.message))
|
|
41
|
+
return undefined;
|
|
42
|
+
return input.message.role === "assistant" ? input.message : undefined;
|
|
43
|
+
}
|
|
44
|
+
function eventType(input) {
|
|
45
|
+
return isObject(input) && typeof input.type === "string" ? input.type : "";
|
|
46
|
+
}
|
|
47
|
+
function hasModelRequestMetadata(message) {
|
|
48
|
+
return (typeof message.api === "string" &&
|
|
49
|
+
typeof message.provider === "string" &&
|
|
50
|
+
typeof message.model === "string");
|
|
51
|
+
}
|
|
52
|
+
function usageFromMessage(message) {
|
|
53
|
+
if (!isObject(message.usage))
|
|
54
|
+
return zeroModelUsage;
|
|
55
|
+
return {
|
|
56
|
+
cache_creation_input_tokens: numberField(message.usage.cacheWrite),
|
|
57
|
+
cache_read_input_tokens: numberField(message.usage.cacheRead),
|
|
58
|
+
input_tokens: numberField(message.usage.input),
|
|
59
|
+
output_tokens: numberField(message.usage.output),
|
|
60
|
+
speed: speedField(message),
|
|
61
|
+
};
|
|
62
|
+
}
|
|
63
|
+
function speedField(message) {
|
|
64
|
+
if (message.speed === "standard" || message.speed === "fast") {
|
|
65
|
+
return message.speed;
|
|
66
|
+
}
|
|
67
|
+
if (isObject(message.model)) {
|
|
68
|
+
const speed = message.model.speed;
|
|
69
|
+
if (speed === "standard" || speed === "fast")
|
|
70
|
+
return speed;
|
|
71
|
+
}
|
|
72
|
+
return null;
|
|
73
|
+
}
|
|
74
|
+
function numberField(value) {
|
|
75
|
+
return typeof value === "number" && Number.isFinite(value) ? value : 0;
|
|
76
|
+
}
|
|
77
|
+
function toJsonObject(value) {
|
|
78
|
+
return value;
|
|
79
|
+
}
|
|
80
|
+
function isObject(value) {
|
|
81
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
82
|
+
}
|
|
@@ -0,0 +1,392 @@
|
|
|
1
|
+
const DEFAULT_ACCESS = {
|
|
2
|
+
enabled: true,
|
|
3
|
+
permission: "allow",
|
|
4
|
+
};
|
|
5
|
+
const DEFAULT_TOOL_CONFIRMATION_TIMEOUT_MS = 5 * 60 * 1000;
|
|
6
|
+
export class PiToolPermissionBridge {
|
|
7
|
+
opts;
|
|
8
|
+
pending = new Map();
|
|
9
|
+
publicToolUseIdsBySession = new Map();
|
|
10
|
+
suppressedPiToolUseIdsBySession = new Map();
|
|
11
|
+
permissionDeniedPiToolCallIdsBySession = new Map();
|
|
12
|
+
constructor(opts = {}) {
|
|
13
|
+
this.opts = opts;
|
|
14
|
+
}
|
|
15
|
+
access(workspaceId, sessionId, toolName) {
|
|
16
|
+
return this.opts.access?.(workspaceId, sessionId, toolName) ?? DEFAULT_ACCESS;
|
|
17
|
+
}
|
|
18
|
+
wrapTool(workspaceId, sessionId, toolName, tool, getEmitter) {
|
|
19
|
+
return {
|
|
20
|
+
...tool,
|
|
21
|
+
execute: async (piToolCallId, params, signal, onUpdate, ctx) => {
|
|
22
|
+
const access = this.access(workspaceId, sessionId, toolName);
|
|
23
|
+
if (!access.enabled) {
|
|
24
|
+
this.markPermissionDenied(sessionId, piToolCallId);
|
|
25
|
+
throw new Error(`Builtin tool ${toolName} is disabled`);
|
|
26
|
+
}
|
|
27
|
+
const input = isRecord(params) ? params : {};
|
|
28
|
+
const materialized = await this.publishToolUse({
|
|
29
|
+
workspaceId,
|
|
30
|
+
sessionId,
|
|
31
|
+
toolName,
|
|
32
|
+
piToolCallId,
|
|
33
|
+
input,
|
|
34
|
+
permission: access.permission,
|
|
35
|
+
signal,
|
|
36
|
+
getEmitter,
|
|
37
|
+
});
|
|
38
|
+
if (access.permission === "deny") {
|
|
39
|
+
this.markPermissionDenied(sessionId, piToolCallId);
|
|
40
|
+
throw new Error(`Builtin tool ${toolName} is denied by policy`);
|
|
41
|
+
}
|
|
42
|
+
if (access.permission === "ask") {
|
|
43
|
+
if (!materialized.confirmation) {
|
|
44
|
+
throw new Error(`Builtin tool ${toolName} confirmation was not registered`);
|
|
45
|
+
}
|
|
46
|
+
const confirmation = await materialized.confirmation;
|
|
47
|
+
if (confirmation.result === "deny") {
|
|
48
|
+
this.markPermissionDenied(sessionId, piToolCallId);
|
|
49
|
+
throw new Error(confirmation.denyMessage ?? `Builtin tool ${toolName} was denied`);
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
return tool.execute(piToolCallId, params, signal, onUpdate, ctx);
|
|
53
|
+
},
|
|
54
|
+
};
|
|
55
|
+
}
|
|
56
|
+
claimConfirmation(workspaceId, sessionId, event) {
|
|
57
|
+
const pending = this.pending.get(event.tool_use_id);
|
|
58
|
+
if (!pending)
|
|
59
|
+
return undefined;
|
|
60
|
+
if (pending.workspaceId !== workspaceId || pending.sessionId !== sessionId) {
|
|
61
|
+
return undefined;
|
|
62
|
+
}
|
|
63
|
+
return () => {
|
|
64
|
+
this.pending.delete(event.tool_use_id);
|
|
65
|
+
pending.cleanup();
|
|
66
|
+
pending.resolve({
|
|
67
|
+
result: event.result,
|
|
68
|
+
denyMessage: event.deny_message,
|
|
69
|
+
});
|
|
70
|
+
};
|
|
71
|
+
}
|
|
72
|
+
rejectSession(sessionId, error) {
|
|
73
|
+
for (const [id, pending] of this.pending) {
|
|
74
|
+
if (pending.sessionId !== sessionId)
|
|
75
|
+
continue;
|
|
76
|
+
this.pending.delete(id);
|
|
77
|
+
pending.cleanup();
|
|
78
|
+
pending.reject(error);
|
|
79
|
+
}
|
|
80
|
+
this.publicToolUseIdsBySession.delete(sessionId);
|
|
81
|
+
this.suppressedPiToolUseIdsBySession.delete(sessionId);
|
|
82
|
+
this.permissionDeniedPiToolCallIdsBySession.delete(sessionId);
|
|
83
|
+
}
|
|
84
|
+
publicToolUseIdForPiToolCallId(sessionId, piToolCallId) {
|
|
85
|
+
return this.publicToolUseIdsBySession.get(sessionId)?.get(piToolCallId);
|
|
86
|
+
}
|
|
87
|
+
suppressPiToolUse(sessionId, piToolCallId) {
|
|
88
|
+
return (this.suppressedPiToolUseIdsBySession.get(sessionId)?.has(piToolCallId) ===
|
|
89
|
+
true);
|
|
90
|
+
}
|
|
91
|
+
permissionDenied(sessionId, piToolCallId) {
|
|
92
|
+
return (this.permissionDeniedPiToolCallIdsBySession
|
|
93
|
+
.get(sessionId)
|
|
94
|
+
?.has(piToolCallId) === true);
|
|
95
|
+
}
|
|
96
|
+
/**
|
|
97
|
+
* MCP variant of `publishToolUse` (plan 0122 §4.4): same id-bind-before-
|
|
98
|
+
* execute contract and the SAME pending-confirmation store — inbound
|
|
99
|
+
* `user.tool_confirmation` claims (`claimConfirmation`) work identically
|
|
100
|
+
* for MCP and builtin tools; the events service dispatch is unchanged.
|
|
101
|
+
*
|
|
102
|
+
* Extra guarantee (terminal-result rule): if the Pi signal aborts between
|
|
103
|
+
* emit and bind, the bind callback immediately emits a synthetic
|
|
104
|
+
* `oma.mcp_tool_result` so the persisted `agent.mcp_tool_use` can never be
|
|
105
|
+
* orphaned by that race.
|
|
106
|
+
*/
|
|
107
|
+
async publishMcpToolUse(opts) {
|
|
108
|
+
const emit = opts.getEmitter();
|
|
109
|
+
if (!emit) {
|
|
110
|
+
throw new Error(`No active runtime consumer for MCP tool ${opts.toolName}`);
|
|
111
|
+
}
|
|
112
|
+
return new Promise((resolve, reject) => {
|
|
113
|
+
let released = false;
|
|
114
|
+
let aborted = false;
|
|
115
|
+
let toolUseId;
|
|
116
|
+
let releaseToolUseId;
|
|
117
|
+
let confirmation;
|
|
118
|
+
const cleanup = () => {
|
|
119
|
+
if (released)
|
|
120
|
+
return;
|
|
121
|
+
released = true;
|
|
122
|
+
opts.signal?.removeEventListener("abort", onAbort);
|
|
123
|
+
};
|
|
124
|
+
const onAbort = () => {
|
|
125
|
+
aborted = true;
|
|
126
|
+
cleanup();
|
|
127
|
+
reject(new Error(`MCP tool ${opts.toolName} aborted`));
|
|
128
|
+
};
|
|
129
|
+
opts.signal?.addEventListener("abort", onAbort, { once: true });
|
|
130
|
+
try {
|
|
131
|
+
emit({
|
|
132
|
+
type: "oma.mcp_tool_use",
|
|
133
|
+
piToolCallId: opts.piToolCallId,
|
|
134
|
+
mcpServerName: opts.mcpServerName,
|
|
135
|
+
name: opts.toolName,
|
|
136
|
+
input: opts.input,
|
|
137
|
+
evaluatedPermission: opts.permission,
|
|
138
|
+
bindToolUseId: (boundToolUseId, release) => {
|
|
139
|
+
if (aborted) {
|
|
140
|
+
// The use event was persisted after the caller already saw the
|
|
141
|
+
// abort; close the loop with a terminal result and release.
|
|
142
|
+
opts.getEmitter()?.({
|
|
143
|
+
type: "oma.mcp_tool_result",
|
|
144
|
+
mcpToolUseId: boundToolUseId,
|
|
145
|
+
content: [
|
|
146
|
+
{ type: "text", text: `MCP tool ${opts.toolName} aborted` },
|
|
147
|
+
],
|
|
148
|
+
isError: true,
|
|
149
|
+
});
|
|
150
|
+
release("interrupted");
|
|
151
|
+
return;
|
|
152
|
+
}
|
|
153
|
+
toolUseId = boundToolUseId;
|
|
154
|
+
releaseToolUseId = release;
|
|
155
|
+
this.bindPublicToolUseId(opts.sessionId, opts.piToolCallId, boundToolUseId);
|
|
156
|
+
cleanup();
|
|
157
|
+
confirmation =
|
|
158
|
+
opts.permission === "ask"
|
|
159
|
+
? this.registerPendingConfirmation({
|
|
160
|
+
workspaceId: opts.workspaceId,
|
|
161
|
+
sessionId: opts.sessionId,
|
|
162
|
+
toolName: opts.toolName,
|
|
163
|
+
toolLabel: `MCP tool ${opts.toolName}`,
|
|
164
|
+
piToolCallId: opts.piToolCallId,
|
|
165
|
+
toolUseId: boundToolUseId,
|
|
166
|
+
signal: opts.signal,
|
|
167
|
+
releaseToolUseId: release,
|
|
168
|
+
})
|
|
169
|
+
: undefined;
|
|
170
|
+
resolve({
|
|
171
|
+
toolUseId: boundToolUseId,
|
|
172
|
+
confirmation: confirmation?.promise,
|
|
173
|
+
});
|
|
174
|
+
},
|
|
175
|
+
rejectToolUse: (error) => {
|
|
176
|
+
if (toolUseId) {
|
|
177
|
+
this.forgetPublicToolUseId(opts.sessionId, opts.piToolCallId);
|
|
178
|
+
this.pending.delete(toolUseId);
|
|
179
|
+
}
|
|
180
|
+
if (confirmation) {
|
|
181
|
+
confirmation.reject(error);
|
|
182
|
+
}
|
|
183
|
+
else {
|
|
184
|
+
releaseToolUseId?.();
|
|
185
|
+
}
|
|
186
|
+
cleanup();
|
|
187
|
+
reject(error);
|
|
188
|
+
},
|
|
189
|
+
});
|
|
190
|
+
}
|
|
191
|
+
catch (error) {
|
|
192
|
+
cleanup();
|
|
193
|
+
reject(toError(error));
|
|
194
|
+
}
|
|
195
|
+
});
|
|
196
|
+
}
|
|
197
|
+
async publishToolUse(opts) {
|
|
198
|
+
const emit = opts.getEmitter();
|
|
199
|
+
if (!emit) {
|
|
200
|
+
throw new Error(`No active runtime consumer for builtin tool ${opts.toolName}`);
|
|
201
|
+
}
|
|
202
|
+
return new Promise((resolve, reject) => {
|
|
203
|
+
let released = false;
|
|
204
|
+
let toolUseId;
|
|
205
|
+
let releaseToolUseId;
|
|
206
|
+
let confirmation;
|
|
207
|
+
const cleanup = () => {
|
|
208
|
+
if (released)
|
|
209
|
+
return;
|
|
210
|
+
released = true;
|
|
211
|
+
opts.signal?.removeEventListener("abort", onAbort);
|
|
212
|
+
};
|
|
213
|
+
const onAbort = () => {
|
|
214
|
+
cleanup();
|
|
215
|
+
reject(new Error(`Builtin tool ${opts.toolName} aborted`));
|
|
216
|
+
};
|
|
217
|
+
opts.signal?.addEventListener("abort", onAbort, { once: true });
|
|
218
|
+
try {
|
|
219
|
+
emit({
|
|
220
|
+
type: "oma.tool_permission_use",
|
|
221
|
+
piToolCallId: opts.piToolCallId,
|
|
222
|
+
name: opts.toolName,
|
|
223
|
+
input: opts.input,
|
|
224
|
+
evaluatedPermission: opts.permission,
|
|
225
|
+
bindToolUseId: (boundToolUseId, release) => {
|
|
226
|
+
toolUseId = boundToolUseId;
|
|
227
|
+
releaseToolUseId = release;
|
|
228
|
+
this.bindPublicToolUseId(opts.sessionId, opts.piToolCallId, boundToolUseId);
|
|
229
|
+
cleanup();
|
|
230
|
+
confirmation =
|
|
231
|
+
opts.permission === "ask"
|
|
232
|
+
? this.registerPendingConfirmation({
|
|
233
|
+
...opts,
|
|
234
|
+
toolUseId: boundToolUseId,
|
|
235
|
+
releaseToolUseId: release,
|
|
236
|
+
})
|
|
237
|
+
: undefined;
|
|
238
|
+
resolve({
|
|
239
|
+
toolUseId: boundToolUseId,
|
|
240
|
+
confirmation: confirmation?.promise,
|
|
241
|
+
});
|
|
242
|
+
},
|
|
243
|
+
rejectToolUse: (error) => {
|
|
244
|
+
if (toolUseId) {
|
|
245
|
+
this.forgetPublicToolUseId(opts.sessionId, opts.piToolCallId);
|
|
246
|
+
this.pending.delete(toolUseId);
|
|
247
|
+
}
|
|
248
|
+
if (confirmation) {
|
|
249
|
+
confirmation.reject(error);
|
|
250
|
+
}
|
|
251
|
+
else {
|
|
252
|
+
releaseToolUseId?.();
|
|
253
|
+
}
|
|
254
|
+
cleanup();
|
|
255
|
+
reject(error);
|
|
256
|
+
},
|
|
257
|
+
});
|
|
258
|
+
}
|
|
259
|
+
catch (error) {
|
|
260
|
+
cleanup();
|
|
261
|
+
reject(toError(error));
|
|
262
|
+
}
|
|
263
|
+
});
|
|
264
|
+
}
|
|
265
|
+
registerPendingConfirmation(opts) {
|
|
266
|
+
const label = opts.toolLabel ?? `Builtin tool ${opts.toolName}`;
|
|
267
|
+
let rejectPending;
|
|
268
|
+
const promise = new Promise((resolve, reject) => {
|
|
269
|
+
let released = false;
|
|
270
|
+
let timer;
|
|
271
|
+
const cleanup = (reason) => {
|
|
272
|
+
if (released)
|
|
273
|
+
return;
|
|
274
|
+
released = true;
|
|
275
|
+
if (timer)
|
|
276
|
+
clearTimeout(timer);
|
|
277
|
+
opts.signal?.removeEventListener("abort", onAbort);
|
|
278
|
+
opts.releaseToolUseId(reason);
|
|
279
|
+
};
|
|
280
|
+
const onAbort = () => {
|
|
281
|
+
this.pending.delete(opts.toolUseId);
|
|
282
|
+
cleanup("interrupted");
|
|
283
|
+
reject(new Error(`${label} aborted`));
|
|
284
|
+
};
|
|
285
|
+
opts.signal?.addEventListener("abort", onAbort, { once: true });
|
|
286
|
+
const timeoutMs = this.opts.timeoutMs ?? DEFAULT_TOOL_CONFIRMATION_TIMEOUT_MS;
|
|
287
|
+
if (timeoutMs > 0) {
|
|
288
|
+
timer = setTimeout(() => {
|
|
289
|
+
this.pending.delete(opts.toolUseId);
|
|
290
|
+
this.markPermissionDenied(opts.sessionId, opts.piToolCallId);
|
|
291
|
+
cleanup("timeout");
|
|
292
|
+
reject(Object.assign(new Error(`${label} confirmation timed out`), {
|
|
293
|
+
omaConfirmationTimeout: true,
|
|
294
|
+
}));
|
|
295
|
+
}, timeoutMs);
|
|
296
|
+
}
|
|
297
|
+
rejectPending = (error) => {
|
|
298
|
+
this.pending.delete(opts.toolUseId);
|
|
299
|
+
cleanup();
|
|
300
|
+
reject(error);
|
|
301
|
+
};
|
|
302
|
+
this.pending.set(opts.toolUseId, {
|
|
303
|
+
workspaceId: opts.workspaceId,
|
|
304
|
+
sessionId: opts.sessionId,
|
|
305
|
+
piToolCallId: opts.piToolCallId,
|
|
306
|
+
resolve,
|
|
307
|
+
reject,
|
|
308
|
+
cleanup,
|
|
309
|
+
});
|
|
310
|
+
});
|
|
311
|
+
return {
|
|
312
|
+
promise,
|
|
313
|
+
reject: rejectPending,
|
|
314
|
+
};
|
|
315
|
+
}
|
|
316
|
+
bindPublicToolUseId(sessionId, piToolCallId, publicToolUseId) {
|
|
317
|
+
let ids = this.publicToolUseIdsBySession.get(sessionId);
|
|
318
|
+
if (!ids) {
|
|
319
|
+
ids = new Map();
|
|
320
|
+
this.publicToolUseIdsBySession.set(sessionId, ids);
|
|
321
|
+
}
|
|
322
|
+
ids.set(piToolCallId, publicToolUseId);
|
|
323
|
+
let suppressed = this.suppressedPiToolUseIdsBySession.get(sessionId);
|
|
324
|
+
if (!suppressed) {
|
|
325
|
+
suppressed = new Set();
|
|
326
|
+
this.suppressedPiToolUseIdsBySession.set(sessionId, suppressed);
|
|
327
|
+
}
|
|
328
|
+
suppressed.add(piToolCallId);
|
|
329
|
+
}
|
|
330
|
+
forgetPublicToolUseId(sessionId, piToolCallId) {
|
|
331
|
+
const ids = this.publicToolUseIdsBySession.get(sessionId);
|
|
332
|
+
ids?.delete(piToolCallId);
|
|
333
|
+
if (ids?.size === 0)
|
|
334
|
+
this.publicToolUseIdsBySession.delete(sessionId);
|
|
335
|
+
const suppressed = this.suppressedPiToolUseIdsBySession.get(sessionId);
|
|
336
|
+
suppressed?.delete(piToolCallId);
|
|
337
|
+
if (suppressed?.size === 0) {
|
|
338
|
+
this.suppressedPiToolUseIdsBySession.delete(sessionId);
|
|
339
|
+
}
|
|
340
|
+
}
|
|
341
|
+
markPermissionDenied(sessionId, piToolCallId) {
|
|
342
|
+
let denied = this.permissionDeniedPiToolCallIdsBySession.get(sessionId);
|
|
343
|
+
if (!denied) {
|
|
344
|
+
denied = new Set();
|
|
345
|
+
this.permissionDeniedPiToolCallIdsBySession.set(sessionId, denied);
|
|
346
|
+
}
|
|
347
|
+
denied.add(piToolCallId);
|
|
348
|
+
}
|
|
349
|
+
}
|
|
350
|
+
export function createStoreBackedBuiltinToolAccessResolver(opts) {
|
|
351
|
+
return (workspaceId, sessionId, toolName, context) => {
|
|
352
|
+
const session = opts.sessions.retrieveAny(workspaceId, sessionId);
|
|
353
|
+
const agentId = session?.agent.id ?? context?.agentId;
|
|
354
|
+
const agentVersion = session?.agent.version ?? context?.agentVersion;
|
|
355
|
+
if (!agentId || agentVersion === undefined) {
|
|
356
|
+
return { enabled: false, permission: "deny" };
|
|
357
|
+
}
|
|
358
|
+
const agent = opts.agents.retrieveVersion(workspaceId, agentId, agentVersion);
|
|
359
|
+
if (!agent)
|
|
360
|
+
return { enabled: false, permission: "deny" };
|
|
361
|
+
return resolveBuiltinToolAccessForAgent(agent, toolName);
|
|
362
|
+
};
|
|
363
|
+
}
|
|
364
|
+
export function resolveBuiltinToolAccessForAgent(agent, toolName) {
|
|
365
|
+
const toolsets = agent.tools.filter((tool) => tool.type === "agent_toolset_20260401");
|
|
366
|
+
if (toolsets.length !== 1)
|
|
367
|
+
return { enabled: false, permission: "deny" };
|
|
368
|
+
const [toolset] = toolsets;
|
|
369
|
+
const defaultConfig = toolset.default_config;
|
|
370
|
+
const config = toolset.configs?.find((item) => item.name === toolName);
|
|
371
|
+
return {
|
|
372
|
+
enabled: config?.enabled ?? defaultConfig?.enabled ?? true,
|
|
373
|
+
permission: policyToPermission(config?.permission_policy?.type ??
|
|
374
|
+
defaultConfig?.permission_policy?.type ??
|
|
375
|
+
"always_allow"),
|
|
376
|
+
};
|
|
377
|
+
}
|
|
378
|
+
function policyToPermission(policy) {
|
|
379
|
+
if (policy === "always_allow")
|
|
380
|
+
return "allow";
|
|
381
|
+
if (policy === "always_ask")
|
|
382
|
+
return "ask";
|
|
383
|
+
if (policy === "never_allow")
|
|
384
|
+
return "deny";
|
|
385
|
+
return "deny";
|
|
386
|
+
}
|
|
387
|
+
function isRecord(value) {
|
|
388
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
389
|
+
}
|
|
390
|
+
function toError(error) {
|
|
391
|
+
return error instanceof Error ? error : new Error(String(error));
|
|
392
|
+
}
|