From 8f059c969c778f561f0cb63fd5bf0907defdb25c Mon Sep 17 00:00:00 2001 From: "michael.zhang" Date: Tue, 23 Jun 2026 02:25:26 +0800 Subject: [PATCH 1/3] feat: extend tenant cloud provider orchestration --- .env.example | 6 +- apps/admin-web/src/app/useWorkspaceActions.ts | 68 +- .../src/pages/modals/WorkspaceCreateModal.tsx | 111 ++- .../modals/WorkspaceCreateSandboxStep.tsx | 36 +- .../src/pages/workspaces/TenantView.tsx | 16 +- .../workspaces/WorkspaceOnboardingConfig.ts | 14 +- .../workspaces/WorkspaceOnboardingSteps.tsx | 67 +- .../workspaces/WorkspaceOnboardingView.tsx | 65 +- .../src/pages/workspaces/cloudProviders.ts | 19 +- .../src/cloud/aliyunOpenApi.ts | 126 +++ .../src/contracts/schemas.ts | 55 +- apps/control-plane-api/src/files/artifacts.ts | 10 +- apps/control-plane-api/src/files/files.ts | 8 +- .../src/files/objectStorage.ts | 99 +- .../src/files/workspaceStorage.ts | 58 +- apps/control-plane-api/src/infra/env.ts | 52 +- .../src/routes/tenantRoutes.ts | 38 +- .../src/routes/workspaceProvisioning.ts | 30 +- .../src/routes/workspaceRoutes.ts | 118 ++- .../src/runtime/aliyunFcRuntime.ts | 170 ++++ apps/control-plane-api/src/runtime/runner.ts | 34 + .../src/runtime/runtimeCommon.ts | 13 +- .../src/runtime/runtimeManager.ts | 161 +++- .../src/runtime/runtimeResources.ts | 6 +- .../src/runtime/runtimeTools.ts | 6 + .../src/runtime/runtimeTypes.ts | 36 +- .../src/runtime/sandboxConfig.ts | 102 +- .../src/runtime/sandboxConfigTypes.ts | 66 +- .../src/runtime/sandboxPoolManager.ts | 154 ++- .../src/storage/cloudProviderIdentity.ts | 50 +- .../src/storage/storeAgentsEnvironments.ts | 52 +- .../src/storage/storeCore.ts | 68 +- .../src/storage/storeSandboxPool.ts | 36 +- .../src/storage/storeSessionsFiles.ts | 4 +- .../src/storage/storeTenant.ts | 8 +- .../src/storage/storeWorkspace.ts | 19 +- .../src/storage/storeWorkspaceCreate.ts | 223 +++-- .../src/storage/storeWorkspaceProvisioning.ts | 80 +- apps/control-plane-api/src/types/ali-oss.d.ts | 23 + bun.lock | 878 ++++++++++-------- maple.config.example.yaml | 11 + package.json | 7 +- packages/cli/internal/project/manifest.go | 19 +- sandbox.config.yaml | 39 + .../aliyun_provider_orchestration_contract.ts | 172 ++++ tests/contracts/auth_tenant_flow_contract.ts | 4 +- tests/contracts/deployment_contract.ts | 4 +- .../environment_lifecycle_contract.ts | 4 +- tests/contracts/npm_sdk_package_contract.ts | 4 +- tests/contracts/platform_sdk_cli_contract.ts | 4 +- tests/contracts/project_env_contract.ts | 17 + tests/contracts/prototype_console_contract.ts | 13 +- .../tenant_cloud_provider_contract.ts | 57 +- .../contracts/vefaas_provisioner_contract.py | 4 +- ...lcengine_credential_validation_contract.ts | 2 +- ...kspace_onboarding_sandbox_pool_contract.ts | 4 +- .../workspace_runtime_pool_contract.ts | 4 +- tests/e2e/e2e.mjs | 96 +- 58 files changed, 2891 insertions(+), 759 deletions(-) create mode 100644 apps/control-plane-api/src/cloud/aliyunOpenApi.ts create mode 100644 apps/control-plane-api/src/runtime/aliyunFcRuntime.ts create mode 100644 apps/control-plane-api/src/types/ali-oss.d.ts create mode 100644 maple.config.example.yaml create mode 100644 sandbox.config.yaml create mode 100644 tests/contracts/aliyun_provider_orchestration_contract.ts diff --git a/.env.example b/.env.example index 2e99d57..7303b54 100644 --- a/.env.example +++ b/.env.example @@ -1,6 +1,7 @@ # Minimal local Docker settings. -# `scripts/setup-local-docker.sh` creates `.env.local` automatically; copy this -# file only when you want to override ports, database password, or model keys. +# `scripts/setup-local-docker.sh` creates `.env.local` automatically. Prefer +# `maple.config.yaml` for growing project config; use `.env` only for local +# overrides, ports, database password, or model keys. # Web/API ports exposed on the host. MAPLE_WEB_PORT=8080 @@ -22,6 +23,7 @@ MAPLE_DEV_LOGIN=true MAPLE_DEV_API_KEY=maple_dev_key MAPLE_LOCAL_MODEL_CONFIG_FILE=config/local-model.json MAPLE_SEED_DEMO_DATA=false +MAPLE_SANDBOX_CONFIG=sandbox.config.yaml # Required when OpenMaple runs in a container and controls the host Docker daemon. # The setup script fills this with an absolute path under this repo. diff --git a/apps/admin-web/src/app/useWorkspaceActions.ts b/apps/admin-web/src/app/useWorkspaceActions.ts index 4b85e35..09ccbeb 100644 --- a/apps/admin-web/src/app/useWorkspaceActions.ts +++ b/apps/admin-web/src/app/useWorkspaceActions.ts @@ -90,48 +90,74 @@ export function useWorkspaceActions(input: { customModelConfigs: OnboardingCustomModelConfig[]; apiKeyName: string; connectedCloudProviders: string[]; - runtimeProvider: "local_docker" | "vefaas"; + runtimeProvider: "local_docker" | "vefaas" | "aliyun_fc"; + standbyRuntimeProvider: "local_docker" | "vefaas" | "aliyun_fc" | ""; + artifactProvider: "tos" | "oss"; vefaasAccessKey: string; vefaasSecretKey: string; vefaasRegion: string; - sandboxProvider: "local_docker" | "e2b" | "daytona" | "vefaas"; + aliyunAccessKeyId: string; + aliyunAccessKeySecret: string; + aliyunRegion: string; + sandboxProvider: "local_docker" | "e2b" | "daytona" | "vefaas" | "aliyun_fc"; + standbySandboxProvider: "local_docker" | "e2b" | "daytona" | "vefaas" | "aliyun_fc" | ""; e2bApiKey: string; daytonaServerUrl: string; daytonaApiKey: string; vefaasSandboxFunctionId: string; vefaasSandboxGatewayUrl: string; vefaasSandboxTimeoutMs: number; + aliyunFcFunctionName: string; + aliyunFcInvokeUrl: string; + aliyunFcApiKey: string; sandboxPoolSize: number; }) { + const runtimePoolBase = { + desired_size: onboarding.desiredSize, + min_instances_per_function: onboarding.minInstances, + max_instances_per_function: onboarding.maxInstances, + max_concurrency_per_instance: onboarding.maxConcurrency, + cpu_milli: onboarding.cpuMilli, + memory_mb: onboarding.memoryMb + }; + const runtimePools = [ + { ...runtimePoolBase, provider: onboarding.runtimeProvider, role: "primary", priority: 0, name: `${onboarding.runtimeProvider} primary` }, + ...(onboarding.standbyRuntimeProvider ? [{ ...runtimePoolBase, provider: onboarding.standbyRuntimeProvider, role: "standby", priority: 10, name: `${onboarding.standbyRuntimeProvider} standby` }] : []) + ]; + const sandboxPoolBase = { desired_size: onboarding.sandboxPoolSize, standby_ttl_ms: 30 * 60 * 1000 }; + const sandboxPools = [ + { ...sandboxPoolBase, provider: onboarding.sandboxProvider, role: "primary", priority: 0, name: `${onboarding.sandboxProvider} primary` }, + ...(onboarding.standbySandboxProvider ? [{ ...sandboxPoolBase, provider: onboarding.standbySandboxProvider, role: "standby", priority: 10, name: `${onboarding.standbySandboxProvider} standby` }] : []) + ]; + const needsVolcengine = [onboarding.runtimeProvider, onboarding.standbyRuntimeProvider, onboarding.sandboxProvider, onboarding.standbySandboxProvider].includes("vefaas") || onboarding.artifactProvider === "tos"; + const needsAliyun = [onboarding.runtimeProvider, onboarding.standbyRuntimeProvider, onboarding.sandboxProvider, onboarding.standbySandboxProvider].includes("aliyun_fc") || onboarding.artifactProvider === "oss"; + const needsE2b = onboarding.sandboxProvider === "e2b" || onboarding.standbySandboxProvider === "e2b"; + const needsDaytona = onboarding.sandboxProvider === "daytona" || onboarding.standbySandboxProvider === "daytona"; const result = await apiPost<{ workspace: Workspace; api_key: WorkspaceApiKey }>("/v1/workspace_onboarding", { tenant: { name: onboarding.tenantName, description: onboarding.tenantDescription }, workspace: { name: onboarding.workspaceName, description: onboarding.workspaceDescription, slug: onboarding.workspaceSlug || undefined }, runtime_provider: onboarding.runtimeProvider, - runtime_pool: { - desired_size: onboarding.desiredSize, - min_instances_per_function: onboarding.minInstances, - max_instances_per_function: onboarding.maxInstances, - max_concurrency_per_instance: onboarding.maxConcurrency, - cpu_milli: onboarding.cpuMilli, - memory_mb: onboarding.memoryMb - }, + runtime_pool: runtimePoolBase, + runtime_pools: runtimePools, sandbox_provider: onboarding.sandboxProvider, - sandbox_config: onboarding.sandboxProvider === "vefaas" - ? { vefaas: { function_id: onboarding.vefaasSandboxFunctionId, gateway_url: onboarding.vefaasSandboxGatewayUrl, timeout_ms: onboarding.vefaasSandboxTimeoutMs, workspace_path: "/home/tiger/workspace" } } - : onboarding.sandboxProvider === "local_docker" - ? { local_docker: { image: "node:22-bookworm", networking: { mode: "limited", allow_mcp_servers: true, allow_package_managers: true } } } - : onboarding.sandboxProvider === "daytona" - ? { daytona: { server_url: onboarding.daytonaServerUrl, api_key: onboarding.daytonaApiKey } } - : {}, - sandbox_pool: { desired_size: onboarding.sandboxPoolSize, standby_ttl_ms: 30 * 60 * 1000 }, + sandbox_config: { + vefaas: { function_id: onboarding.vefaasSandboxFunctionId, gateway_url: onboarding.vefaasSandboxGatewayUrl, timeout_ms: onboarding.vefaasSandboxTimeoutMs, workspace_path: "/home/tiger/workspace" }, + aliyun_fc: { function_name: onboarding.aliyunFcFunctionName, invoke_url: onboarding.aliyunFcInvokeUrl, api_key: onboarding.aliyunFcApiKey, timeout_ms: onboarding.vefaasSandboxTimeoutMs, workspace_path: "/workspace" }, + local_docker: { image: "node:22-bookworm", networking: { mode: "limited", allow_mcp_servers: true, allow_package_managers: true } }, + daytona: { server_url: onboarding.daytonaServerUrl, api_key: onboarding.daytonaApiKey } + }, + sandbox_pool: sandboxPoolBase, + sandbox_pools: sandboxPools, + artifact_provider: onboarding.artifactProvider, model_config_ids: onboarding.modelConfigIds, custom_model_configs: onboarding.customModelConfigs.map(({ local_id: _localId, ...config }) => config), api_key: { display_name: onboarding.apiKeyName, scopes: ["control_plane", "data_plane"] }, admin: { email: input.currentUser?.email, name: input.currentUser?.name }, provider_credentials: { - vefaas: (onboarding.runtimeProvider === "vefaas" || onboarding.sandboxProvider === "vefaas") && onboarding.connectedCloudProviders.includes("volcengine") ? { VOLCENGINE_ACCESS_KEY: onboarding.vefaasAccessKey, VOLCENGINE_SECRET_KEY: onboarding.vefaasSecretKey, VEFAAS_REGION: onboarding.vefaasRegion } : {}, - e2b: onboarding.sandboxProvider === "e2b" ? { E2B_API_KEY: onboarding.e2bApiKey } : {}, - daytona: onboarding.sandboxProvider === "daytona" ? { DAYTONA_SERVER_URL: onboarding.daytonaServerUrl, DAYTONA_API_KEY: onboarding.daytonaApiKey } : {} + vefaas: needsVolcengine && onboarding.connectedCloudProviders.includes("volcengine") ? { VOLCENGINE_ACCESS_KEY: onboarding.vefaasAccessKey, VOLCENGINE_SECRET_KEY: onboarding.vefaasSecretKey, VEFAAS_REGION: onboarding.vefaasRegion } : {}, + aliyun: needsAliyun && onboarding.connectedCloudProviders.includes("aliyun") ? { ALIYUN_ACCESS_KEY_ID: onboarding.aliyunAccessKeyId, ALIYUN_ACCESS_KEY_SECRET: onboarding.aliyunAccessKeySecret, ALIYUN_REGION: onboarding.aliyunRegion, ALIYUN_FC_FUNCTION_NAME: onboarding.aliyunFcFunctionName, ALIYUN_FC_INVOKE_URL: onboarding.aliyunFcInvokeUrl, ALIYUN_FC_API_KEY: onboarding.aliyunFcApiKey } : {}, + e2b: needsE2b ? { E2B_API_KEY: onboarding.e2bApiKey } : {}, + daytona: needsDaytona ? { DAYTONA_SERVER_URL: onboarding.daytonaServerUrl, DAYTONA_API_KEY: onboarding.daytonaApiKey } : {} } }); const issuedKey = result.api_key.key ?? ""; diff --git a/apps/admin-web/src/pages/modals/WorkspaceCreateModal.tsx b/apps/admin-web/src/pages/modals/WorkspaceCreateModal.tsx index 1cfdae2..a54b172 100644 --- a/apps/admin-web/src/pages/modals/WorkspaceCreateModal.tsx +++ b/apps/admin-web/src/pages/modals/WorkspaceCreateModal.tsx @@ -1,3 +1,4 @@ +/* eslint-disable max-lines */ import { Fragment, useEffect, useState } from "react"; import { apiGet, apiPost, type ApiList } from "../../api"; import type { JsonRecord, ModelConfig, Workspace, WorkspaceApiKey } from "../../types"; @@ -34,12 +35,19 @@ export function WorkspaceCreateModal(props: { onClose: () => void; onOpenTenantC const [color, setColor] = useState(WS_COLORS[5] ?? WS_COLORS[0]); const [cloudProviders, setCloudProviders] = useState>({}); const [cloudProviderLoading, setCloudProviderLoading] = useState(Boolean(props.tenantId)); - const [sandboxProvider, setSandboxProvider] = useState<"e2b" | "daytona" | "vefaas">("e2b"); + const [runtimeProvider, setRuntimeProvider] = useState<"vefaas" | "aliyun_fc">("vefaas"); + const [standbyRuntimeProvider, setStandbyRuntimeProvider] = useState<"" | "vefaas" | "aliyun_fc">(""); + const [artifactProvider, setArtifactProvider] = useState<"tos" | "oss">("tos"); + const [sandboxProvider, setSandboxProvider] = useState<"e2b" | "daytona" | "vefaas" | "aliyun_fc">("e2b"); + const [standbySandboxProvider, setStandbySandboxProvider] = useState<"" | "e2b" | "daytona" | "vefaas" | "aliyun_fc">(""); const [e2bApiKey, setE2bApiKey] = useState(""); const [daytonaServerUrl, setDaytonaServerUrl] = useState(""); const [daytonaApiKey, setDaytonaApiKey] = useState(""); const [vefaasSandboxFunctionId, setVefaasSandboxFunctionId] = useState(""); const [vefaasSandboxGatewayUrl, setVefaasSandboxGatewayUrl] = useState(""); + const [aliyunFcFunctionName, setAliyunFcFunctionName] = useState(""); + const [aliyunFcInvokeUrl, setAliyunFcInvokeUrl] = useState(""); + const [aliyunFcApiKey, setAliyunFcApiKey] = useState(""); const [vefaasSandboxTimeoutInput, setVefaasSandboxTimeoutInput] = useState("3600000"); const [sandboxPoolSizeInput, setSandboxPoolSizeInput] = useState("1"); const [desiredSizeInput, setDesiredSizeInput] = useState("3"); @@ -71,12 +79,19 @@ export function WorkspaceCreateModal(props: { onClose: () => void; onOpenTenantC const sandboxPoolSize = readInt(sandboxPoolSizeInput, 1, 1, MAX_SANDBOX_POOL_SIZE); const volcengineConnected = Boolean(cloudProviders.volcengine?.connected); - const runtimeCredsFilled = volcengineConnected; - const sandboxFilled = sandboxProvider === "e2b" + const aliyunConnected = Boolean(cloudProviders.aliyun?.connected); + const runtimeProviderReady = (provider: "" | "vefaas" | "aliyun_fc") => !provider || (provider === "vefaas" ? volcengineConnected : aliyunConnected); + const runtimeCredsFilled = runtimeProviderReady(runtimeProvider) && runtimeProviderReady(standbyRuntimeProvider); + const sandboxProviderReady = (provider: "" | "e2b" | "daytona" | "vefaas" | "aliyun_fc") => !provider + ? true + : provider === "e2b" ? Boolean(e2bApiKey.trim()) - : sandboxProvider === "daytona" + : provider === "daytona" ? Boolean(daytonaServerUrl.trim() && daytonaApiKey.trim()) - : volcengineConnected && Boolean(vefaasSandboxFunctionId.trim() && vefaasSandboxGatewayUrl.trim()); + : provider === "vefaas" + ? volcengineConnected && Boolean(vefaasSandboxFunctionId.trim() && vefaasSandboxGatewayUrl.trim()) + : aliyunConnected && Boolean(aliyunFcInvokeUrl.trim()); + const sandboxFilled = sandboxProviderReady(sandboxProvider) && sandboxProviderReady(standbySandboxProvider); const credsFilled = runtimeCredsFilled && sandboxFilled; const slugTooShort = Boolean(slug.trim()) && slug.trim().length < 3; @@ -101,7 +116,10 @@ export function WorkspaceCreateModal(props: { onClose: () => void; onOpenTenantC useEffect(() => { if (sandboxProvider === "vefaas" && !volcengineConnected) setSandboxProvider("e2b"); - }, [sandboxProvider, volcengineConnected]); + if (sandboxProvider === "aliyun_fc" && !aliyunConnected) setSandboxProvider("e2b"); + if (runtimeProvider === "vefaas" && !volcengineConnected && aliyunConnected) setRuntimeProvider("aliyun_fc"); + if (runtimeProvider === "aliyun_fc" && !aliyunConnected && volcengineConnected) setRuntimeProvider("vefaas"); + }, [sandboxProvider, volcengineConnected, aliyunConnected, runtimeProvider]); function isStepComplete(index: number) { if (index === 0) return Boolean(name.trim()) && !slugTooShort; @@ -200,6 +218,15 @@ export function WorkspaceCreateModal(props: { onClose: () => void; onOpenTenantC setBusy(true); setError(""); setProvisionLogs(provisioningPlan()); + const runtimePoolBase = { + desired_size: desiredSize, + min_instances_per_function: minInstances, + max_instances_per_function: maxInstances, + max_concurrency_per_instance: maxConcurrency, + cpu_milli: cpuMilli, + memory_mb: memoryMb + }; + const sandboxPoolBase = { desired_size: sandboxPoolSize, standby_ttl_ms: 30 * 60 * 1000 }; const heartbeat = window.setInterval(() => { setProvisionLogs((current) => [...current, provisioningLog("info", L("仍在提交云端初始化请求。", "Still submitting cloud provisioning request."))]); }, 8000); @@ -207,36 +234,32 @@ export function WorkspaceCreateModal(props: { onClose: () => void; onOpenTenantC const result = await apiPost<{ workspace: Workspace; api_key: WorkspaceApiKey; provisioning_logs?: ProvisioningLog[] }>("/v1/workspaces", { tenant_id: props.tenantId || undefined, workspace: { name: trimmed, description: description.trim(), slug: slug.trim() ? normalizedSlug : undefined }, - runtime_provider: "vefaas", + runtime_provider: runtimeProvider, + runtime_pools: [ + { ...runtimePoolBase, provider: runtimeProvider, role: "primary", priority: 0 }, + ...(standbyRuntimeProvider ? [{ ...runtimePoolBase, provider: standbyRuntimeProvider, role: "standby", priority: 10 }] : []) + ], sandbox_provider: sandboxProvider, - sandbox_config: sandboxProvider === "vefaas" - ? { - vefaas: { - function_id: vefaasSandboxFunctionId.trim(), - gateway_url: vefaasSandboxGatewayUrl.trim(), - timeout_ms: vefaasSandboxTimeoutMs, - workspace_path: "/home/tiger/workspace" - } - } - : sandboxProvider === "daytona" - ? { daytona: { server_url: daytonaServerUrl.trim(), api_key: daytonaApiKey.trim() } } - : {}, - sandbox_pool: { desired_size: sandboxPoolSize, standby_ttl_ms: 30 * 60 * 1000 }, - runtime_pool: { - desired_size: desiredSize, - min_instances_per_function: minInstances, - max_instances_per_function: maxInstances, - max_concurrency_per_instance: maxConcurrency, - cpu_milli: cpuMilli, - memory_mb: memoryMb + sandbox_config: { + vefaas: { function_id: vefaasSandboxFunctionId.trim(), gateway_url: vefaasSandboxGatewayUrl.trim(), timeout_ms: vefaasSandboxTimeoutMs, workspace_path: "/home/tiger/workspace" }, + aliyun_fc: { function_name: aliyunFcFunctionName.trim(), invoke_url: aliyunFcInvokeUrl.trim(), api_key: aliyunFcApiKey.trim(), workspace_path: "/workspace" }, + daytona: { server_url: daytonaServerUrl.trim(), api_key: daytonaApiKey.trim() } }, + sandbox_pool: sandboxPoolBase, + sandbox_pools: [ + { ...sandboxPoolBase, provider: sandboxProvider, role: "primary", priority: 0 }, + ...(standbySandboxProvider ? [{ ...sandboxPoolBase, provider: standbySandboxProvider, role: "standby", priority: 10 }] : []) + ], + artifact_provider: artifactProvider, + runtime_pool: runtimePoolBase, model_config_ids: selectedModelIds, member_emails: memberEmails, api_key: { display_name: apiKeyName.trim() || apiKeyPlaceholder, scopes: ["control_plane", "data_plane"] }, provider_credentials: { vefaas: {}, - e2b: sandboxProvider === "e2b" ? { E2B_API_KEY: e2bApiKey.trim() } : {}, - daytona: sandboxProvider === "daytona" ? { DAYTONA_SERVER_URL: daytonaServerUrl.trim(), DAYTONA_API_KEY: daytonaApiKey.trim() } : {} + aliyun: {}, + e2b: sandboxProvider === "e2b" || standbySandboxProvider === "e2b" ? { E2B_API_KEY: e2bApiKey.trim() } : {}, + daytona: sandboxProvider === "daytona" || standbySandboxProvider === "daytona" ? { DAYTONA_SERVER_URL: daytonaServerUrl.trim(), DAYTONA_API_KEY: daytonaApiKey.trim() } : {} } }); const backendLogs = normalizeProvisioningLogs(result.provisioning_logs); @@ -300,22 +323,37 @@ export function WorkspaceCreateModal(props: { onClose: () => void; onOpenTenantC {step === 1 ? (
- {volcengineConnected ? ( +
+ + + +
+ {runtimeProvider === "vefaas" && volcengineConnected ? (
VeFaaS{L("Agent 运行时 provider", "Agent runtime provider")} {L("来自火山引擎", "From Volcengine")}
) : null} + {runtimeProvider === "aliyun_fc" && aliyunConnected ? ( +
+ + Aliyun FC{L("Agent 运行时 provider", "Agent runtime provider")} + {L("来自阿里云", "From Aliyun")} +
+ ) : null}
{L("租户级云凭据", "Tenant-level cloud credentials")}
- {cloudProviderLoading ?
{L("正在读取租户云厂商接入状态…", "Loading tenant cloud provider access…")}
: volcengineConnected ? ( -
{L(`已接入火山引擎 · ${String(cloudProviders.volcengine?.access_key_hint || "")}`, `Volcengine connected · ${String(cloudProviders.volcengine?.access_key_hint || "")}`)}
+ {cloudProviderLoading ?
{L("正在读取租户云厂商接入状态…", "Loading tenant cloud provider access…")}
: runtimeCredsFilled ? ( +
{[ + volcengineConnected ? L(`已接入火山引擎 · ${String(cloudProviders.volcengine?.access_key_hint || "")}`, `Volcengine connected · ${String(cloudProviders.volcengine?.access_key_hint || "")}`) : "", + aliyunConnected ? L(`已接入阿里云 · ${String(cloudProviders.aliyun?.access_key_hint || "")}`, `Aliyun connected · ${String(cloudProviders.aliyun?.access_key_hint || "")}`) : "" + ].filter(Boolean).join(" / ")}
) : (
- {L("租户尚未接入火山引擎,因此不能创建云端 Runtime。", "Volcengine is not connected for this tenant, so cloud Runtime cannot be created.")} + {L("租户尚未接入所选 Runtime 云厂商。", "The selected runtime cloud provider is not connected for this tenant.")} {props.onOpenTenantCloudAccess ? ( ) : null} + {props.aliyunConnected ? ( + + ) : null}
+ {props.sandboxProvider === "e2b" ? (
E2B {L("凭据", "credentials")}
@@ -56,13 +79,20 @@ export function WorkspaceCreateSandboxStep(props: {
- ) : ( + ) : props.sandboxProvider === "vefaas" ? (
VeFaaS Sandbox
+ ) : ( +
+
Aliyun FC Sandbox
+ + + +
)}