@openai/agents-core
Version:
The OpenAI Agents SDK is a lightweight yet powerful framework for building multi-agent workflows.
747 lines • 29.7 kB
JavaScript
import { isDir, isGitRepo, isMount, } from "./entries/index.mjs";
import { normalizePathGrant, } from "./pathGrants.mjs";
import { normalizePermissions } from "./permissions.mjs";
import { normalizeEntryGroup, normalizeGroup, normalizeUser, } from "./users.mjs";
import { DEFAULT_REMOTE_MOUNT_COMMAND_ALLOWLIST } from "./shared/remoteMountCommandAllowlist.mjs";
import { hasBackslashPathSeparator, hasParentPathSegment, normalizePosixPath, relativePosixPathWithinRoot, } from "./shared/posixPath.mjs";
import { isRecord } from "./shared/typeGuards.mjs";
import { SandboxGitSubpathError } from "./errors.mjs";
import { typedMountProviderConfig } from "./shared/typedMountConfig.mjs";
export class Environment {
value;
resolver;
ephemeral;
description;
constructor(entry) {
if (entry instanceof EnvValueReference) {
return entry;
}
if (isSerializedEnvValueReference(entry)) {
return parseEnvValueReference(entry);
}
if (entry instanceof Environment) {
this.value = entry.value;
this.resolver = entry.resolver;
this.ephemeral = entry.ephemeral;
this.description = entry.description;
return;
}
if (typeof entry === 'string') {
this.value = entry;
this.resolver = undefined;
this.ephemeral = false;
this.description = undefined;
return;
}
if (typeof entry === 'function') {
this.value = '';
this.resolver = entry;
this.ephemeral = false;
this.description = undefined;
return;
}
this.value = entry.value;
this.resolver = entry.resolve;
this.ephemeral = entry.ephemeral ?? false;
this.description = entry.description;
}
async resolve() {
const value = this.resolver ? await this.resolver() : this.value;
if (typeof value !== 'string') {
throw new Error('Environment resolvers must return a string.');
}
return value;
}
init() {
return {
...this.normalized(),
...(this.resolver ? { resolve: this.resolver } : {}),
};
}
normalized() {
return {
value: this.value,
...(this.ephemeral ? { ephemeral: true } : {}),
...(this.description ? { description: this.description } : {}),
};
}
}
export class Manifest {
version;
root;
entries;
environment;
users;
groups;
extraPathGrants;
remoteMountCommandAllowlist;
constructor(init = {}) {
if (MOUNT_CREDENTIAL_EXPOSURE_POLICY_KEYS.some((key) => key in init)) {
throw new TypeError('In-container mount credential exposure must be configured on a trusted Manifest instance, not in a manifest init object.');
}
rejectKnownSnakeCaseKeys(init, ['extra_path_grants', 'remote_mount_command_allowlist'], 'sandbox manifest config');
this.version = init.version ?? 1;
this.root = normalizeRoot(init.root ?? '/workspace');
this.entries = normalizeEntries(init.entries ?? {});
this.environment = Object.fromEntries(Object.entries(init.environment ?? {}).map(([key, value]) => [
key,
new Environment(value),
]));
this.users = (init.users ?? []).map((user) => normalizeUser(user));
this.groups = (init.groups ?? []).map((group) => normalizeGroup(group));
this.extraPathGrants = (init.extraPathGrants ?? []).map((grant) => normalizePathGrant(grant));
this.remoteMountCommandAllowlist = [
...(init.remoteMountCommandAllowlist ??
DEFAULT_REMOTE_MOUNT_COMMAND_ALLOWLIST),
];
}
/**
* Returns a trusted manifest that acknowledges exposure of mount-scoped
* credentials for exact in-container mount paths.
*
* This application-side policy is runtime-only. It is not accepted from
* manifest init objects and is never serialized into sandbox session state.
*/
withInContainerMountCredentialExposureAcknowledged(...mountPaths) {
return withInContainerMountCredentialExposureAcknowledgement(this, 'mount_scoped', mountPaths);
}
/**
* Returns a trusted manifest that acknowledges exposure of broad credential
* authority for exact in-container mount paths.
*
* Broad authority includes ambient credentials, workload or managed
* identity, and external credential files. This application-side policy is
* runtime-only. It is not accepted from manifest init objects and is never
* serialized into sandbox session state.
*/
withInContainerMountBroadCredentialExposureAcknowledged(...mountPaths) {
return withInContainerMountCredentialExposureAcknowledgement(this, 'broad', mountPaths);
}
validatedEntries() {
for (const _entry of this.iterEntries()) {
continue;
}
return { ...this.entries };
}
*iterEntries() {
const queue = Object.entries(this.entries)
.sort(([left], [right]) => left.localeCompare(right))
.map(([path, entry]) => {
const normalizedPath = normalizeRelativePath(path);
return {
logicalPath: normalizedPath,
absolutePath: joinAbsolutePath(this.root, normalizedPath),
entry,
depth: 1,
};
});
while (queue.length > 0) {
const current = queue.shift();
yield current;
if (!isDir(current.entry) || !current.entry.children) {
continue;
}
const children = Object.entries(current.entry.children)
.sort(([left], [right]) => left.localeCompare(right))
.map(([childPath, childEntry]) => {
const normalizedChildPath = normalizeRelativePath(childPath);
const logicalPath = current.logicalPath
? `${current.logicalPath}/${normalizedChildPath}`
: normalizedChildPath;
return {
logicalPath,
absolutePath: joinAbsolutePath(this.root, logicalPath),
entry: childEntry,
depth: current.depth + 1,
};
});
queue.unshift(...children);
}
}
ephemeralEntryPaths() {
const paths = new Set();
for (const { logicalPath, entry } of this.iterEntries()) {
if (entry.ephemeral) {
paths.add(logicalPath);
}
}
return paths;
}
/**
* Returns mount targets in most-specific-first order for path resolution.
*/
mountTargets() {
return collectMountTargets(this).sort(compareMountTargetsForResolution);
}
/**
* Returns mount targets in parent-first order for filesystem materializers.
*/
mountTargetsForMaterialization() {
return collectMountTargets(this).sort(compareMountTargetsForMaterialization);
}
ephemeralMountTargets() {
return this.mountTargets().filter(({ entry }) => entry.ephemeral);
}
ephemeralPersistencePaths() {
const paths = this.ephemeralEntryPaths();
// Ephemeral mounts are keyed by their effective mount path because mountPath can
// differ from the manifest entry's logical path.
for (const { mountPath } of this.ephemeralMountTargets()) {
const relativePath = relativePathWithinRoot(this.root, mountPath);
if (relativePath !== null) {
paths.add(relativePath);
}
}
return paths;
}
async resolveEnvironment(overrides = {}) {
return {
...Object.fromEntries(await Promise.all(Object.entries(this.environment).map(async ([key, value]) => [
key,
await value.resolve(),
]))),
...overrides,
};
}
describe(depth = 1) {
return renderManifestDescription(this, { depth }).text;
}
}
const envValueReferenceRegistry = new Map();
/**
* A JSON-serializable reference to a runtime sandbox environment value.
*
* Subclasses must serialize only non-secret lookup metadata. Register each
* subclass in every process that reconstructs manifests before parsing JSON.
*/
export class EnvValueReference extends Environment {
static type = '';
constructor(options = {}) {
super({
value: '',
...(options.ephemeral ? { ephemeral: true } : {}),
...(options.description ? { description: options.description } : {}),
});
}
get type() {
return this.constructor.type;
}
init() {
return parseEnvValueReference(serializeEnvValueReference(this));
}
normalized() {
throw new TypeError('EnvValueReference cannot be normalized to an EnvValue. Use serializeEnvValueReference() instead.');
}
toJSON() {
return serializeEnvValueReference(this);
}
}
/**
* Registers one environment reference subtype for JSON reconstruction.
*
* The parser receives persisted data and must validate it before constructing
* a reference, including allowlisting any secret-store lookup keys.
*
* Returns an unregister callback for test isolation and controlled module
* lifecycle cleanup. Conflicting registrations fail instead of replacing the
* active parser.
*/
export function registerEnvValueReference(referenceClass, parse) {
if (typeof referenceClass !== 'function') {
throw new TypeError('EnvValueReference registration requires a class.');
}
if (!Object.prototype.hasOwnProperty.call(referenceClass, 'type')) {
throw new TypeError(`${envValueReferenceClassName(referenceClass)} must declare its own static type.`);
}
const type = referenceClass.type;
if (typeof type !== 'string' || type.trim() === '') {
throw new TypeError('EnvValueReference type must be a non-empty string.');
}
const existing = envValueReferenceRegistry.get(type);
if (existing) {
throw new TypeError(`Environment value reference type "${type}" is already registered by ${envValueReferenceClassName(existing.referenceClass)}.`);
}
const registration = {
referenceClass,
parse,
};
envValueReferenceRegistry.set(type, registration);
return () => {
if (envValueReferenceRegistry.get(type) === registration) {
envValueReferenceRegistry.delete(type);
}
};
}
export function serializeEnvValueReference(reference) {
const registration = envValueReferenceRegistry.get(reference.type);
if (!registration ||
reference.constructor !== registration.referenceClass) {
throw new TypeError(`${reference.constructor.name || 'EnvValueReference subclass'} must be registered with its own static type before serialization.`);
}
const serialized = reference.serialize();
if (!isRecord(serialized)) {
throw new TypeError(`${reference.constructor.name || 'EnvValueReference subclass'}.serialize() must return a record.`);
}
if ('value' in serialized) {
throw new TypeError(`${reference.constructor.name || 'EnvValueReference subclass'}.serialize() must not return the reserved "value" field.`);
}
return {
...serialized,
type: reference.type,
};
}
export function isEnvValueReference(value) {
return value instanceof EnvValueReference;
}
export function isSerializedEnvValueReference(value) {
return (isRecord(value) && typeof value.type === 'string' && !('value' in value));
}
function parseEnvValueReference(serialized) {
const registration = envValueReferenceRegistry.get(serialized.type);
if (!registration) {
const knownTypes = [...envValueReferenceRegistry.keys()].sort().join(', ');
throw new TypeError(`Unknown environment value reference type "${serialized.type}". Registered types: ${knownTypes || '<none>'}.`);
}
const { type: _type, ...payload } = serialized;
const reference = registration.parse(payload);
if (!(reference instanceof EnvValueReference) ||
reference.constructor !== registration.referenceClass ||
reference.type !== serialized.type) {
throw new TypeError(`Parser for environment value reference type "${serialized.type}" must return an instance of ${envValueReferenceClassName(registration.referenceClass)}.`);
}
return reference;
}
function envValueReferenceClassName(referenceClass) {
const name = referenceClass.name;
return typeof name === 'string' && name ? name : 'EnvValueReference subclass';
}
export const MOUNT_CREDENTIAL_EXPOSURE_POLICY_KEYS = [
'in_container_mount_credential_exposure_allowed_paths',
'_in_container_mount_credential_exposure_allowed_paths',
'inContainerMountCredentialExposureAllowedPaths',
'_inContainerMountCredentialExposureAllowedPaths',
'in_container_mount_credential_exposure_acknowledged_paths',
'_in_container_mount_credential_exposure_acknowledged_paths',
'inContainerMountCredentialExposureAcknowledgedPaths',
'_inContainerMountCredentialExposureAcknowledgedPaths',
'in_container_mount_broad_credential_exposure_acknowledged_paths',
'_in_container_mount_broad_credential_exposure_acknowledged_paths',
'inContainerMountBroadCredentialExposureAcknowledgedPaths',
'_inContainerMountBroadCredentialExposureAcknowledgedPaths',
];
const inContainerMountCredentialExposurePolicy = new WeakMap();
export function manifestAcknowledgesInContainerMountCredentialExposure(manifest, mountPath, authority) {
const acknowledged = inContainerMountCredentialExposurePolicy.get(manifest)?.[authority];
if (!acknowledged) {
return false;
}
const normalized = normalizeRoot(mountPath);
const relative = relativePathWithinRoot(manifest.root, normalized);
return ((relative !== null && acknowledged.has(`relative:${relative}`)) ||
acknowledged.has(`absolute:${normalized}`));
}
export function copyManifestMountCredentialExposurePolicy(target, ...sources) {
const policy = {
mount_scoped: new Set(),
broad: new Set(),
};
for (const source of sources) {
const sourcePolicy = inContainerMountCredentialExposurePolicy.get(source);
for (const authority of ['mount_scoped', 'broad']) {
for (const path of sourcePolicy?.[authority] ?? []) {
policy[authority].add(path);
}
}
}
if (policy.mount_scoped.size > 0 || policy.broad.size > 0) {
inContainerMountCredentialExposurePolicy.set(target, policy);
}
}
export function replaceManifestMountCredentialExposurePolicy(target, source) {
const policy = inContainerMountCredentialExposurePolicy.get(source);
if (!policy || (policy.mount_scoped.size === 0 && policy.broad.size === 0)) {
inContainerMountCredentialExposurePolicy.delete(target);
return;
}
inContainerMountCredentialExposurePolicy.set(target, {
mount_scoped: new Set(policy.mount_scoped),
broad: new Set(policy.broad),
});
}
export function cloneManifest(manifest) {
const cloned = new Manifest({
version: manifest.version,
root: manifest.root,
entries: structuredClone(manifest.entries ?? {}),
environment: Object.fromEntries(Object.entries(manifest.environment ?? {}).map(([key, value]) => [
key,
value instanceof Environment ? value.init() : value,
])),
users: structuredClone(manifest.users ?? []),
groups: structuredClone(manifest.groups ?? []),
extraPathGrants: structuredClone(manifest.extraPathGrants ?? []),
remoteMountCommandAllowlist: structuredClone(manifest.remoteMountCommandAllowlist ??
DEFAULT_REMOTE_MOUNT_COMMAND_ALLOWLIST),
});
if (manifest instanceof Manifest) {
copyManifestMountCredentialExposurePolicy(cloned, manifest);
}
return cloned;
}
function normalizeManifestMountPolicyPath(root, path) {
const trimmed = path.trim();
if (!trimmed.startsWith('/')) {
const relative = normalizeRelativePath(trimmed);
if (!relative) {
throw new Error('Mount credential exposure path must identify a non-root path.');
}
return `relative:${relative}`;
}
const normalized = normalizeRoot(trimmed);
if (normalized === '/' || normalized === normalizeRoot(root)) {
throw new Error('Mount credential exposure path must identify a non-root path.');
}
const relative = relativePathWithinRoot(root, normalized);
return relative === null ? `absolute:${normalized}` : `relative:${relative}`;
}
function withInContainerMountCredentialExposureAcknowledgement(manifest, authority, mountPaths) {
if (mountPaths.length === 0) {
throw new TypeError('At least one in-container mount path is required.');
}
const trusted = cloneManifest(manifest);
const current = inContainerMountCredentialExposurePolicy.get(manifest);
const policy = {
mount_scoped: new Set(current?.mount_scoped ?? []),
broad: new Set(current?.broad ?? []),
};
for (const mountPath of mountPaths) {
policy[authority].add(normalizeManifestMountPolicyPath(manifest.root, mountPath));
}
inContainerMountCredentialExposurePolicy.set(trusted, policy);
return trusted;
}
export function normalizeRelativePath(path) {
const trimmed = path.trim();
if (!trimmed || trimmed === '.') {
return '';
}
if (hasBackslashPathSeparator(path)) {
throw new Error(`Manifest path "${path}" must use "/" separators.`);
}
if (trimmed.startsWith('/')) {
throw new Error(`Manifest path "${path}" must be relative.`);
}
if (hasParentPathSegment(path)) {
throw new Error(`Manifest path "${path}" must not contain parent segments.`);
}
const normalized = normalizePosixPath(path);
return normalized === '.' ? '' : normalized;
}
function normalizeGitRepoSubpath(repo, subpath) {
const trimmed = subpath.trim();
let reason;
if (subpath === '' || normalizePosixPath(trimmed) === '.') {
return '';
}
if (!trimmed) {
reason = 'empty';
}
else if (hasBackslashPathSeparator(subpath) ||
/^[A-Za-z]:($|\/)/.test(trimmed)) {
reason = 'windows_path';
}
else if (trimmed.startsWith('/')) {
reason = 'absolute';
}
else if (hasParentPathSegment(subpath)) {
reason = 'parent_traversal';
}
if (reason) {
throw new SandboxGitSubpathError(`git_repo subpath must be a relative path inside the repository: ${subpath}`, { repo, subpath, reason });
}
return normalizePosixPath(trimmed);
}
export function renderManifestDescription(manifest, options = {}) {
const maxDepth = options.depth ?? null;
const maxLines = options.maxLines ?? 120;
const maxChars = options.maxChars ?? 12_000;
const lines = [manifest.root];
let renderedChars = manifest.root.length;
const entries = [...manifest.iterEntries()];
let renderedPaths = 0;
for (const { logicalPath, absolutePath, entry, depth } of entries) {
if (maxDepth !== null && depth > maxDepth) {
continue;
}
if (lines.length >= maxLines) {
break;
}
const indent = depth <= 1 ? '' : ' '.repeat(depth - 1);
const name = logicalPath.split('/').pop() ?? logicalPath;
const label = entry.type === 'dir' ? `${name}/` : name;
const description = entry.description ? ` - ${entry.description}` : '';
const line = `${indent}${label} # ${absolutePath}${description}`;
if (!lineFits(lines, line, maxChars, renderedChars)) {
break;
}
lines.push(line);
renderedChars += lineCharCost(lines, line);
renderedPaths += 1;
}
const totalPaths = entries.filter(({ depth }) => maxDepth === null || depth <= maxDepth).length;
if (renderedPaths < totalPaths) {
renderedChars = appendLineIfFits(lines, `... (truncated ${totalPaths - renderedPaths} additional paths)`, maxChars, renderedChars);
appendLineIfFits(lines, `Hint: increase depth, maxLines, or maxChars to see more of the manifest layout.`, maxChars, renderedChars);
}
return {
text: lines.join('\n'),
renderedPaths,
totalPaths,
};
}
function appendLineIfFits(lines, line, maxChars, currentChars) {
if (!lineFits(lines, line, maxChars, currentChars)) {
return currentChars;
}
lines.push(line);
return currentChars + lineCharCost(lines, line);
}
function lineFits(lines, line, maxChars, currentChars) {
return currentChars + lineCharCost(lines, line) <= maxChars;
}
function lineCharCost(lines, line) {
return (lines.length > 0 ? 1 : 0) + line.length;
}
function normalizeRoot(root) {
const trimmed = root.trim();
if (hasBackslashPathSeparator(root)) {
throw new Error(`Manifest root "${root}" must use "/" separators.`);
}
if (!trimmed.startsWith('/')) {
throw new Error(`Manifest root "${root}" must be absolute.`);
}
if (hasParentPathSegment(root)) {
throw new Error(`Manifest root "${root}" must not contain parent segments.`);
}
return normalizePosixPath(root);
}
function joinAbsolutePath(root, relativePath) {
const normalizedRoot = normalizeRoot(root);
const normalizedRelativePath = normalizeRelativePath(relativePath);
if (!normalizedRelativePath) {
return normalizedRoot;
}
if (normalizedRoot === '/') {
return `/${normalizedRelativePath}`;
}
return `${normalizedRoot}/${normalizedRelativePath}`;
}
function resolveMountPathForRoot(root, absolutePath, mountPath) {
if (mountPath === undefined) {
return absolutePath;
}
if (mountPath.startsWith('/')) {
return normalizeRoot(mountPath);
}
return joinAbsolutePath(root, mountPath);
}
function relativePathWithinRoot(root, path) {
const normalizedRoot = normalizeRoot(root);
const normalizedPath = normalizeRoot(path);
return relativePosixPathWithinRoot(normalizedRoot, normalizedPath);
}
function collectMountTargets(manifest) {
const targets = [];
for (const iterEntry of manifest.iterEntries()) {
if (!isMount(iterEntry.entry)) {
continue;
}
targets.push({
...iterEntry,
entry: iterEntry.entry,
mountPath: resolveMountPathForRoot(manifest.root, iterEntry.absolutePath, iterEntry.entry.mountPath),
});
}
return targets;
}
function compareMountTargetsForResolution(left, right) {
// Path resolution needs the most specific nested mount before its parent.
const depthDelta = mountPathDepth(right.mountPath) - mountPathDepth(left.mountPath);
return depthDelta || left.mountPath.localeCompare(right.mountPath);
}
function compareMountTargetsForMaterialization(left, right) {
// Mount creation needs parents first so broader targets do not replace children.
const depthDelta = mountPathDepth(left.mountPath) - mountPathDepth(right.mountPath);
return depthDelta || left.mountPath.localeCompare(right.mountPath);
}
function mountPathDepth(path) {
return path.split('/').filter(Boolean).length;
}
function normalizeEntries(entries, parentPath = '', seenPaths = new Set()) {
const normalizedEntries = {};
for (const [path, entry] of Object.entries(entries)) {
const normalizedPath = normalizeRelativePath(path);
const logicalPath = parentPath
? `${parentPath}/${normalizedPath}`
: normalizedPath;
if (seenPaths.has(logicalPath)) {
throw new Error(`Manifest path "${path}" duplicates normalized path "${logicalPath}".`);
}
seenPaths.add(logicalPath);
if (Object.prototype.hasOwnProperty.call(normalizedEntries, normalizedPath)) {
throw new Error(`Manifest path "${path}" duplicates normalized path "${normalizedPath}".`);
}
normalizedEntries[normalizedPath] = normalizeEntry(entry, logicalPath, seenPaths);
}
return normalizedEntries;
}
function normalizeEntry(entry, logicalPath, seenPaths) {
const normalized = structuredClone(entry);
if (normalized.permissions !== undefined) {
normalized.permissions = normalizePermissions(normalized.permissions);
}
if (normalized.group !== undefined) {
normalized.group = normalizeEntryGroup(normalized.group);
}
if (isDir(normalized) && normalized.children) {
normalized.children = normalizeEntries(normalized.children, logicalPath, seenPaths);
}
if (isGitRepo(normalized)) {
const repo = normalized.repo;
if (!repo || typeof repo !== 'string' || !repo.trim()) {
throw new Error('git_repo entries must include a non-empty repo.');
}
normalized.repo = repo.trim();
// URL-like values pass through to host Git without an HTTPS prefix.
if (normalized.repo.startsWith('-') && normalized.repo.includes('://')) {
throw new Error('git_repo repository URL must not start with "-".');
}
normalized.host = normalizeGitHost(normalized.host);
if (normalized.subpath !== undefined) {
normalized.subpath = normalizeGitRepoSubpath(normalized.repo, normalized.subpath);
}
}
if (isMount(normalized)) {
normalized.ephemeral = normalized.ephemeral ?? true;
rejectKnownSnakeCaseMountKeys(normalized);
if (normalized.mountPath !== undefined) {
normalized.mountPath = normalizeMountPath(normalized.mountPath);
}
normalized.readOnly = normalized.readOnly ?? true;
if (normalized.mountStrategy !== undefined) {
normalized.mountStrategy = normalizeMountStrategy(normalized.mountStrategy);
}
normalizeTypedMountProvider(normalized);
}
return normalized;
}
function normalizeGitHost(host) {
const normalizedHost = (host ?? 'github.com').trim();
if (!normalizedHost) {
throw new Error('git_repo host must be non-empty.');
}
return normalizedHost;
}
function normalizeMountPath(path) {
const trimmed = path.trim();
if (!trimmed) {
throw new Error('Mount path must not be empty.');
}
if (hasBackslashPathSeparator(path)) {
throw new Error('Mount path must use "/" separators.');
}
if (!trimmed.startsWith('/')) {
return normalizeRelativePath(trimmed);
}
return normalizeRoot(trimmed);
}
function normalizeMountStrategy(strategy) {
if (!isRecord(strategy)) {
throw new Error('Mount strategy must be an object.');
}
if (typeof strategy.type !== 'string' || !strategy.type.trim()) {
throw new Error('Mount strategy must include a non-empty type.');
}
const normalized = {
...strategy,
type: strategy.type.trim(),
};
rejectKnownSnakeCaseKeys(normalized, ['driver_options'], 'mount strategy');
if (isRecord(normalized.pattern)) {
rejectKnownSnakeCaseKeys(normalized.pattern, [
'remote_name',
'extra_args',
'config_file_path',
'nfs_addr',
'nfs_mount_options',
'allow_other',
'log_type',
'log_level',
'cache_type',
'cache_path',
'cache_size_mb',
'block_cache_block_size_mb',
'block_cache_disk_timeout_sec',
'file_cache_timeout_sec',
'file_cache_max_size_mb',
'attr_cache_timeout_sec',
'entry_cache_timeout_sec',
'negative_entry_cache_timeout_sec',
], 'mount strategy pattern');
if (isRecord(normalized.pattern.options)) {
rejectKnownSnakeCaseKeys(normalized.pattern.options, ['endpoint_url', 'mount_target_ip', 'access_point', 'extra_options'], 'mount strategy pattern options');
}
}
return normalized;
}
function normalizeTypedMountProvider(entry) {
if (!isMount(entry) || entry.type === 'mount') {
return;
}
const { provider, config } = typedMountProviderConfig(entry);
entry.provider = entry.provider ?? provider;
entry.config = {
...(entry.config ?? {}),
...config,
};
}
function rejectKnownSnakeCaseMountKeys(entry) {
rejectKnownSnakeCaseKeys(entry, [
'mount_path',
'read_only',
'mount_strategy',
'client_id',
'client_secret',
'access_id',
'access_key_id',
'access_token',
'secret_access_key',
'session_token',
'service_account_credentials',
'service_account_file',
'endpoint_url',
'account_id',
'account_name',
'account_key',
'custom_domain',
'file_system_id',
'mount_target_ip',
'access_point',
'extra_options',
's3_provider',
'identity_client_id',
'box_config_file',
'config_credentials',
'box_sub_type',
'root_folder_id',
'owned_by',
], 'sandbox manifest entries');
}
function rejectKnownSnakeCaseKeys(value, keys, source) {
const found = keys.find((key) => key in value);
if (!found) {
return;
}
throw new Error(`Use camelCase config keys in ${source}; snake_case key "${found}" is not supported.`);
}
//# sourceMappingURL=manifest.mjs.map