@@ -18,9 +18,16 @@ import {
1818 E2BProfiles ,
1919 e2bRecipeHash ,
2020 HeldSecret ,
21+ OrbContainers ,
22+ orbRecipeHash ,
2123 selfhostProviders ,
2224} from "./deploy/selfhost/alchemy-resources.ts" ;
23- import { E2B_ORB_PROFILES } from "./packages/domain/src/settings/runner-profile.ts" ;
25+ import CLOUDFLARE_ORB_PROFILES from "./packages/domain/cloudflare-orb-profiles.json" with {
26+ type : "json" ,
27+ } ;
28+ import E2B_ORB_PROFILES from "./packages/domain/e2b-orb-profiles.json" with {
29+ type : "json" ,
30+ } ;
2431import { decodeGitHubAppDeploymentConfiguration } from "./packages/domain/src/source-control/github-app-configuration.ts" ;
2532
2633type RecordValue < T > = T extends Record < string , infer Value > ? Value : never ;
@@ -81,6 +88,28 @@ const defaultE2BOrbProfile = E2B_ORB_PROFILES.find(
8188) ;
8289if ( defaultE2BOrbProfile === undefined )
8390 throw new Error ( "The default E2B Orb profile is missing from the catalog." ) ;
91+ const defaultCloudflareOrbProfile = CLOUDFLARE_ORB_PROFILES . find (
92+ ( { id } ) => id === "cf.standard-2" ,
93+ ) ;
94+ if ( defaultCloudflareOrbProfile === undefined )
95+ throw new Error (
96+ "The default Cloudflare Orb profile is missing from the catalog." ,
97+ ) ;
98+ /** What every deployed Orb size offers; see RunnerProfileCatalogConfiguration. */
99+ const deployedOrbCapabilities = [
100+ "git" ,
101+ "environment-variables" ,
102+ "internet-access" ,
103+ "persistent-workspace" ,
104+ "pause-resume" ,
105+ ] as const ;
106+ const cloudflareOrbProfiles = CLOUDFLARE_ORB_PROFILES . map ( ( profile ) => ( {
107+ ...profile ,
108+ adapter : "cloudflare" ,
109+ isolation : "container" ,
110+ availability : "available" ,
111+ capabilities : deployedOrbCapabilities ,
112+ } ) ) ;
84113
85114const productionWorkerBindings = <
86115 Groups extends Record < string , Record < string , unknown > > ,
@@ -264,6 +293,16 @@ export default Alchemy.Stack(
264293 e2bApiKey : Config . option ( Config . redacted ( "E2B_API_KEY" ) ) ,
265294 githubApp : Config . option ( Config . redacted ( "DX_INTEGRATION_GITHUB_APP" ) ) ,
266295 sarvamApiKey : Config . option ( Config . redacted ( "SARVAM_API_KEY" ) ) ,
296+ exaApiKey : Config . option ( Config . redacted ( "EXA_API_KEY" ) ) ,
297+ plugins : Config . withDefault ( Config . string ( "DX_DEPLOYMENT_PLUGINS" ) , "" ) ,
298+ // Orb providers the deployment installs: e2b, cloudflare, or both.
299+ orbProviders : Config . withDefault (
300+ Config . string ( "DX_DEPLOYMENT_ORB_PROVIDERS" ) ,
301+ "e2b" ,
302+ ) ,
303+ orbWorkerName : Config . option (
304+ Config . nonEmptyString ( "DX_DEPLOYMENT_ORB_WORKER_NAME" ) ,
305+ ) ,
267306 workloadIdentitySigningKeys : Config . option (
268307 Config . redacted ( "DX_WORKLOAD_IDENTITY_SIGNING_KEYS" ) ,
269308 ) ,
@@ -323,13 +362,29 @@ export default Alchemy.Stack(
323362 const integrations = new Set (
324363 configuration . integrations . split ( "," ) . filter ( Boolean ) ,
325364 ) ;
326- const e2bApiKey = selfhost
327- ? ( yield * HeldSecret ( "E2BApiKey" , {
328- value : Option . getOrUndefined ( configuration . e2bApiKey ) ,
329- } ) ) . value
330- : Option . isSome ( configuration . e2bApiKey )
331- ? configuration . e2bApiKey . value
332- : yield * Effect . die ( "E2B_API_KEY is required." ) ;
365+ const orbProviders = new Set (
366+ configuration . orbProviders . split ( "," ) . filter ( Boolean ) ,
367+ ) ;
368+ if (
369+ orbProviders . size === 0 ||
370+ [ ...orbProviders ] . some (
371+ ( provider ) => provider !== "e2b" && provider !== "cloudflare" ,
372+ )
373+ )
374+ return yield * Effect . die (
375+ "DX_DEPLOYMENT_ORB_PROVIDERS must name e2b, cloudflare, or both." ,
376+ ) ;
377+ const e2bInstalled = orbProviders . has ( "e2b" ) ;
378+ const cloudflareInstalled = orbProviders . has ( "cloudflare" ) ;
379+ const e2bApiKey = ! e2bInstalled
380+ ? undefined
381+ : selfhost
382+ ? ( yield * HeldSecret ( "E2BApiKey" , {
383+ value : Option . getOrUndefined ( configuration . e2bApiKey ) ,
384+ } ) ) . value
385+ : Option . isSome ( configuration . e2bApiKey )
386+ ? configuration . e2bApiKey . value
387+ : yield * Effect . die ( "E2B_API_KEY is required." ) ;
333388 const githubApp = selfhost
334389 ? integrations . has ( "github" )
335390 ? ( yield * HeldSecret ( "GitHubAppSecret" , {
@@ -351,22 +406,59 @@ export default Alchemy.Stack(
351406 value : Option . getOrUndefined ( configuration . bitbucketOAuth ) ,
352407 } ) ) . value
353408 : Option . getOrUndefined ( configuration . bitbucketOAuth ) ;
409+ // The Speech plugin's deployment-scope Sarvam key. The self-host
410+ // integration keeps its `sarvam` name and HeldSecret ID.
354411 const sarvamApiKey =
355412 selfhost && integrations . has ( "sarvam" )
356413 ? ( yield * HeldSecret ( "SarvamApiKey" , {
357414 value : Option . getOrUndefined ( configuration . sarvamApiKey ) ,
358415 } ) ) . value
359416 : Option . getOrUndefined ( configuration . sarvamApiKey ) ;
360- const selfhostProfiles = selfhost
361- ? yield * E2BProfiles ( "E2BProfiles" , {
362- apiKey : e2bApiKey ,
363- deploymentName : Option . getOrElse (
364- configuration . deploymentName ,
365- ( ) => "" ,
366- ) ,
367- recipeHash : yield * Effect . promise ( ( ) => e2bRecipeHash ( ) ) ,
368- } as never )
369- : undefined ;
417+ // Plugin installation is deployment scope. Hosted targets install every
418+ // first-party plugin; self-host installs what `pnpm dx:deploy` recorded.
419+ const installedPlugins = selfhost ? configuration . plugins : "search,speech" ;
420+ const exaApiKey = selfhost
421+ ? integrations . has ( "exa" )
422+ ? ( yield * HeldSecret ( "ExaApiKey" , {
423+ value : Option . getOrUndefined ( configuration . exaApiKey ) ,
424+ } ) ) . value
425+ : undefined
426+ : Option . getOrUndefined ( configuration . exaApiKey ) ;
427+ const selfhostProfiles =
428+ selfhost && e2bApiKey !== undefined
429+ ? yield * E2BProfiles ( "E2BProfiles" , {
430+ apiKey : e2bApiKey ,
431+ deploymentName : Option . getOrElse (
432+ configuration . deploymentName ,
433+ ( ) => "" ,
434+ ) ,
435+ recipeHash : yield * Effect . promise ( ( ) => e2bRecipeHash ( ) ) ,
436+ } as never )
437+ : undefined ;
438+ // Cloudflare Containers runs in this account: the Orb Worker owns one
439+ // container per Thread, and Core binds its namespace across scripts.
440+ const orbContainers = ! cloudflareInstalled
441+ ? undefined
442+ : Option . isSome ( configuration . orbWorkerName )
443+ ? yield * OrbContainers ( "OrbContainers" , {
444+ workerName : configuration . orbWorkerName . value ,
445+ recipeHash : orbRecipeHash ( import . meta. dirname ) ,
446+ } )
447+ : yield * Effect . die (
448+ "DX_DEPLOYMENT_ORB_WORKER_NAME is required for Cloudflare Containers." ,
449+ ) ;
450+ const defaultOrbProfileId = e2bInstalled
451+ ? defaultE2BOrbProfile . id
452+ : defaultCloudflareOrbProfile . id ;
453+ const runnerProfileCatalog = ( e2bProfiles : ReadonlyArray < unknown > ) =>
454+ JSON . stringify ( {
455+ version : 1 ,
456+ defaultProfileId : defaultOrbProfileId ,
457+ profiles : [
458+ ...e2bProfiles ,
459+ ...( cloudflareInstalled ? cloudflareOrbProfiles : [ ] ) ,
460+ ] ,
461+ } ) ;
370462
371463 if (
372464 configuration . turnstileTestKeys &&
@@ -495,12 +587,17 @@ export default Alchemy.Stack(
495587 : { } ) ,
496588 DX_AUTH_URL : configuration . origin ,
497589 DX_AUTH_TRUSTED_ORIGINS : configuration . origin ,
498- DX_E2B_TEMPLATE :
499- selfhostProfiles ?. defaultTemplate ??
500- `${ configuration . e2bTemplate } -${ defaultE2BOrbProfile . templateSuffix } ` ,
501- DX_E2B_TEMPLATE_BUILD_ID :
502- selfhostProfiles ?. defaultBuildId ?? configuration . e2bTemplateBuildId ,
503- DX_E2B_TIMEOUT_MS : "300000" ,
590+ ...( e2bInstalled
591+ ? {
592+ DX_E2B_TEMPLATE :
593+ selfhostProfiles ?. defaultTemplate ??
594+ `${ configuration . e2bTemplate } -${ defaultE2BOrbProfile . templateSuffix } ` ,
595+ DX_E2B_TEMPLATE_BUILD_ID :
596+ selfhostProfiles ?. defaultBuildId ??
597+ configuration . e2bTemplateBuildId ,
598+ DX_E2B_TIMEOUT_MS : "300000" ,
599+ }
600+ : { } ) ,
504601 ...( Option . isSome ( configuration . workspaceInactivityMs )
505602 ? {
506603 DX_WORKSPACE_INACTIVITY_MS : String (
@@ -518,50 +615,39 @@ export default Alchemy.Stack(
518615 DX_MIGRATION_MANIFEST_VERSION : String ( migrationManifest . version ) ,
519616 DX_RUNNER_PROFILE_CATALOG :
520617 selfhostProfiles === undefined
521- ? JSON . stringify ( {
522- version : 1 ,
523- defaultProfileId : defaultE2BOrbProfile . id ,
524- profiles : E2B_ORB_PROFILES . map ( ( profile ) => ( {
525- ...profile ,
526- adapter : "e2b" ,
527- template : `${ configuration . e2bTemplate } -${ profile . templateSuffix } ` ,
528- isolation : "sandbox" ,
529- availability : "available" ,
530- capabilities : [
531- "git" ,
532- "environment-variables" ,
533- "internet-access" ,
534- "persistent-workspace" ,
535- "pause-resume" ,
536- ] ,
537- } ) ) ,
538- } )
618+ ? runnerProfileCatalog (
619+ e2bInstalled
620+ ? E2B_ORB_PROFILES . map ( ( profile ) => ( {
621+ ...profile ,
622+ adapter : "e2b" ,
623+ template : `${ configuration . e2bTemplate } -${ profile . templateSuffix } ` ,
624+ isolation : "sandbox" ,
625+ availability : "available" ,
626+ capabilities : deployedOrbCapabilities ,
627+ } ) )
628+ : [ ] ,
629+ )
539630 : Output . map ( selfhostProfiles . profilesJson , ( profilesJson ) =>
540- JSON . stringify ( {
541- version : 1 ,
542- defaultProfileId : defaultE2BOrbProfile . id ,
543- profiles : JSON . parse ( profilesJson ) . map (
631+ runnerProfileCatalog (
632+ JSON . parse ( profilesJson ) . map (
544633 ( profile : Record < string , unknown > ) => ( {
545634 ...profile ,
546635 adapter : "e2b" ,
547636 isolation : "sandbox" ,
548637 availability : "available" ,
549- capabilities : [
550- "git" ,
551- "environment-variables" ,
552- "internet-access" ,
553- "persistent-workspace" ,
554- "pause-resume" ,
555- ] ,
638+ capabilities : deployedOrbCapabilities ,
556639 } ) ,
557640 ) ,
558- } ) ,
641+ ) ,
559642 ) ,
560643 DX_SOURCE_CONTROL_SCHEMA_VERSION : sourceControlSchemaVersion ,
561644 DX_SOURCE_SHALLOW_CLONE : configuration . sourceShallowClone
562645 ? "true"
563646 : "false" ,
564647 DX_MANAGED_SSH_SIGNING_ENABLED : "true" ,
648+ ...( installedPlugins === ""
649+ ? { }
650+ : { DX_INSTALLED_PLUGINS : installedPlugins } ) ,
565651 ...( configuration . signupEnabled ? { DX_SIGNUP_ENABLED : "true" } : { } ) ,
566652 ...( Option . isSome ( configuration . githubCopilotClientId )
567653 ? {
@@ -603,8 +689,9 @@ export default Alchemy.Stack(
603689 DX_CONFIG_ENCRYPTION_KEYS : encryptionKeyring ,
604690 DX_INTEGRATION_GITHUB_APP : githubApp ,
605691 DX_WORKLOAD_IDENTITY_SIGNING_KEYS : workloadIdentitySigningKeys ,
606- E2B_API_KEY : e2bApiKey ,
692+ ... ( e2bApiKey === undefined ? { } : { E2B_API_KEY : e2bApiKey } ) ,
607693 ...( sarvamApiKey !== undefined ? { SARVAM_API_KEY : sarvamApiKey } : { } ) ,
694+ ...( exaApiKey !== undefined ? { EXA_API_KEY : exaApiKey } : { } ) ,
608695 ...( bitbucketOAuth !== undefined
609696 ? {
610697 DX_INTEGRATION_BITBUCKET_OAUTH : bitbucketOAuth ,
@@ -619,7 +706,17 @@ export default Alchemy.Stack(
619706 "workers-ai" : {
620707 [ aiBinding . name ] : Cloudflare . Workers . AI ( aiBinding . name ) ,
621708 } ,
622- "durable-object" : durableObjectBindings ,
709+ "durable-object" : {
710+ ...durableObjectBindings ,
711+ ...( orbContainers === undefined
712+ ? { }
713+ : {
714+ ORB_CONTAINER : Cloudflare . DurableObject ( "ORB_CONTAINER" , {
715+ className : "OrbContainerObject" ,
716+ scriptName : orbContainers . workerName ,
717+ } ) ,
718+ } ) ,
719+ } ,
623720 } ) ;
624721
625722 const bootstrap = yield * Command . Exec ( "Bootstrap" , {
@@ -651,7 +748,7 @@ export default Alchemy.Stack(
651748 DX_BOOTSTRAP_REPOSITORY : configuration . bootstrapRepository ,
652749 } ) ,
653750 DX_BOOTSTRAP_DATABASE_ID : database . databaseId ,
654- DX_BOOTSTRAP_RUNNER_PROFILE_ID : defaultE2BOrbProfile . id ,
751+ DX_BOOTSTRAP_RUNNER_PROFILE_ID : defaultOrbProfileId ,
655752 } ,
656753 memo : false ,
657754 timeout : "3 minutes" ,
0 commit comments