2025-08-26 20:49:25 +00:00
/**
* @license
* Copyright 2025 Google LLC
* SPDX-License-Identifier: Apache-2.0
*/
import * as fs from 'node:fs' ;
import * as path from 'node:path' ;
import * as dotenv from 'dotenv' ;
import {
AuthType ,
Config ,
FileDiscoveryService ,
ApprovalMode ,
loadServerHierarchicalMemory ,
2025-10-14 02:31:39 +09:00
GEMINI_DIR ,
2025-08-26 20:49:25 +00:00
DEFAULT_GEMINI_EMBEDDING_MODEL ,
2025-12-01 10:06:13 -08:00
startupProfiler ,
2025-12-15 22:51:23 -05:00
PREVIEW_GEMINI_MODEL ,
2026-01-06 20:09:39 -08:00
homedir ,
2026-01-20 11:38:38 -05:00
GitService ,
2026-02-03 20:16:20 +00:00
fetchAdminControlsOnce ,
getCodeAssistServer ,
ExperimentFlags ,
2026-03-04 15:35:21 -05:00
isHeadlessMode ,
FatalAuthenticationError ,
isCloudShell ,
2026-02-28 02:43:30 +05:30
type TelemetryTarget ,
type ConfigParameters ,
type ExtensionLoader ,
2025-08-26 20:49:25 +00:00
} from '@google/gemini-cli-core' ;
2025-09-03 10:24:48 -04:00
import { logger } from '../utils/logger.js' ;
2025-08-26 20:49:25 +00:00
import type { Settings } from './settings.js' ;
2025-09-03 10:24:48 -04:00
import { type AgentSettings , CoderAgentEvent } from '../types.js' ;
2025-08-26 20:49:25 +00:00
export async function loadConfig (
settings : Settings ,
2025-10-28 09:04:30 -07:00
extensionLoader : ExtensionLoader ,
2025-08-26 20:49:25 +00:00
taskId : string ,
) : Promise < Config > {
const workspaceDir = process . cwd ( ) ;
const adcFilePath = process . env [ 'GOOGLE_APPLICATION_CREDENTIALS' ] ;
2026-01-12 16:46:42 -05:00
const folderTrust =
settings . folderTrust === true ||
process . env [ 'GEMINI_FOLDER_TRUST' ] === 'true' ;
2026-01-20 11:38:38 -05:00
let checkpointing = process . env [ 'CHECKPOINTING' ]
? process . env [ 'CHECKPOINTING' ] === 'true'
: settings . checkpointing ? . enabled ;
if ( checkpointing ) {
if ( ! ( await GitService . verifyGitAvailability ( ) ) ) {
logger . warn (
'[Config] Checkpointing is enabled but git is not installed. Disabling checkpointing.' ,
) ;
checkpointing = false ;
}
}
2025-08-26 20:49:25 +00:00
const configParams : ConfigParameters = {
sessionId : taskId ,
2026-02-06 13:02:57 -05:00
model : PREVIEW_GEMINI_MODEL ,
2025-08-26 20:49:25 +00:00
embeddingModel : DEFAULT_GEMINI_EMBEDDING_MODEL ,
sandbox : undefined , // Sandbox might not be relevant for a server-side agent
targetDir : workspaceDir , // Or a specific directory the agent operates on
debugMode : process.env [ 'DEBUG' ] === 'true' || false ,
question : '' , // Not used in server mode directly like CLI
2025-10-16 12:09:21 -07:00
2026-02-24 22:22:32 +05:30
coreTools : settings.coreTools || settings . tools ? . core || undefined ,
excludeTools : settings.excludeTools || settings . tools ? . exclude || undefined ,
allowedTools : settings.allowedTools || settings . tools ? . allowed || undefined ,
2025-08-26 20:49:25 +00:00
showMemoryUsage : settings.showMemoryUsage || false ,
approvalMode :
process.env [ 'GEMINI_YOLO_MODE' ] === 'true'
? ApprovalMode.YOLO
: ApprovalMode.DEFAULT ,
2025-11-04 07:51:18 -08:00
mcpServers : settings.mcpServers ,
2025-08-26 20:49:25 +00:00
cwd : workspaceDir ,
telemetry : {
enabled : settings.telemetry?.enabled ,
2026-02-10 00:10:15 +00:00
// eslint-disable-next-line @typescript-eslint/no-unsafe-type-assertion
2025-08-26 20:49:25 +00:00
target : settings.telemetry?.target as TelemetryTarget ,
otlpEndpoint :
process.env [ 'OTEL_EXPORTER_OTLP_ENDPOINT' ] ? ?
settings . telemetry ? . otlpEndpoint ,
logPrompts : settings.telemetry?.logPrompts ,
} ,
// Git-aware file filtering settings
fileFiltering : {
respectGitIgnore : settings.fileFiltering?.respectGitIgnore ,
2026-01-27 17:19:13 -08:00
respectGeminiIgnore : settings.fileFiltering?.respectGeminiIgnore ,
2025-08-26 20:49:25 +00:00
enableRecursiveFileSearch :
settings.fileFiltering?.enableRecursiveFileSearch ,
2026-01-27 17:19:13 -08:00
customIgnoreFilePaths : [
. . . ( settings . fileFiltering ? . customIgnoreFilePaths || [ ] ) ,
. . . ( process . env [ 'CUSTOM_IGNORE_FILE_PATHS' ]
? process . env [ 'CUSTOM_IGNORE_FILE_PATHS' ] . split ( path . delimiter )
: [ ] ) ,
] ,
2025-08-26 20:49:25 +00:00
} ,
ideMode : false ,
2026-01-12 16:46:42 -05:00
folderTrust ,
trustedFolder : true ,
2025-10-28 09:04:30 -07:00
extensionLoader ,
2026-01-20 11:38:38 -05:00
checkpointing ,
2026-03-04 15:35:21 -05:00
interactive : ! isHeadlessMode ( ) ,
enableInteractiveShell : ! isHeadlessMode ( ) ,
2026-01-26 15:14:48 +00:00
ptyInfo : 'auto' ,
2025-08-26 20:49:25 +00:00
} ;
2026-01-27 17:19:13 -08:00
const fileService = new FileDiscoveryService ( workspaceDir , {
respectGitIgnore : configParams?.fileFiltering?.respectGitIgnore ,
respectGeminiIgnore : configParams?.fileFiltering?.respectGeminiIgnore ,
customIgnoreFilePaths : configParams?.fileFiltering?.customIgnoreFilePaths ,
} ) ;
2026-01-12 16:46:42 -05:00
const { memoryContent , fileCount , filePaths } =
await loadServerHierarchicalMemory (
workspaceDir ,
[ workspaceDir ] ,
fileService ,
extensionLoader ,
folderTrust ,
) ;
2025-08-26 20:49:25 +00:00
configParams . userMemory = memoryContent ;
configParams . geminiMdFileCount = fileCount ;
2026-01-12 16:46:42 -05:00
configParams . geminiMdFilePaths = filePaths ;
2026-02-03 20:16:20 +00:00
// Set an initial config to use to get a code assist server.
// This is needed to fetch admin controls.
const initialConfig = new Config ( {
2025-08-26 20:49:25 +00:00
. . . configParams ,
} ) ;
2026-02-03 20:16:20 +00:00
const codeAssistServer = getCodeAssistServer ( initialConfig ) ;
const adminControlsEnabled =
initialConfig . getExperiments ( ) ? . flags [ ExperimentFlags . ENABLE_ADMIN_CONTROLS ]
? . boolValue ? ? false ;
// Initialize final config parameters to the previous parameters.
// If no admin controls are needed, these will be used as-is for the final
// config.
const finalConfigParams = { . . . configParams } ;
if ( adminControlsEnabled ) {
const adminSettings = await fetchAdminControlsOnce (
codeAssistServer ,
adminControlsEnabled ,
) ;
// Admin settings are able to be undefined if unset, but if any are present,
// we should initialize them all.
// If any are present, undefined settings should be treated as if they were
// set to false.
// If NONE are present, disregard admin settings entirely, and pass the
// final config as is.
if ( Object . keys ( adminSettings ) . length !== 0 ) {
2026-02-03 16:19:14 -05:00
finalConfigParams . disableYoloMode = ! adminSettings . strictModeDisabled ;
finalConfigParams . mcpEnabled = adminSettings . mcpSetting ? . mcpEnabled ;
2026-02-03 20:16:20 +00:00
finalConfigParams . extensionsEnabled =
2026-02-03 16:19:14 -05:00
adminSettings . cliFeatureSetting ? . extensionsSetting ? . extensionsEnabled ;
2025-08-26 20:49:25 +00:00
}
}
2026-02-03 20:16:20 +00:00
const config = new Config ( finalConfigParams ) ;
// Needed to initialize ToolRegistry, and git checkpointing if enabled
await config . initialize ( ) ;
2026-02-28 23:03:08 +05:30
await config . waitForMcpInit ( ) ;
2026-02-03 20:16:20 +00:00
startupProfiler . flush ( config ) ;
await refreshAuthentication ( config , adcFilePath , 'Config' ) ;
2025-08-26 20:49:25 +00:00
return config ;
}
export function setTargetDir ( agentSettings : AgentSettings | undefined ) : string {
const originalCWD = process . cwd ( ) ;
const targetDir =
process . env [ 'CODER_AGENT_WORKSPACE_PATH' ] ? ?
( agentSettings ? . kind === CoderAgentEvent . StateAgentSettingsEvent
? agentSettings.workspacePath
: undefined ) ;
if ( ! targetDir ) {
return originalCWD ;
}
logger . info (
` [CoderAgentExecutor] Overriding workspace path to: ${ targetDir } ` ,
) ;
try {
const resolvedPath = path . resolve ( targetDir ) ;
process . chdir ( resolvedPath ) ;
return resolvedPath ;
} catch ( e ) {
logger . error (
` [CoderAgentExecutor] Error resolving workspace path: ${ e } , returning original os.cwd() ` ,
) ;
return originalCWD ;
}
}
export function loadEnvironment ( ) : void {
const envFilePath = findEnvFile ( process . cwd ( ) ) ;
if ( envFilePath ) {
dotenv . config ( { path : envFilePath , override : true } ) ;
}
}
function findEnvFile ( startDir : string ) : string | null {
let currentDir = path . resolve ( startDir ) ;
while ( true ) {
// prefer gemini-specific .env under GEMINI_DIR
2025-10-14 02:31:39 +09:00
const geminiEnvPath = path . join ( currentDir , GEMINI_DIR , '.env' ) ;
2025-08-26 20:49:25 +00:00
if ( fs . existsSync ( geminiEnvPath ) ) {
return geminiEnvPath ;
}
const envPath = path . join ( currentDir , '.env' ) ;
if ( fs . existsSync ( envPath ) ) {
return envPath ;
}
const parentDir = path . dirname ( currentDir ) ;
if ( parentDir === currentDir || ! parentDir ) {
// check .env under home as fallback, again preferring gemini-specific .env
2025-10-14 02:31:39 +09:00
const homeGeminiEnvPath = path . join ( process . cwd ( ) , GEMINI_DIR , '.env' ) ;
2025-08-26 20:49:25 +00:00
if ( fs . existsSync ( homeGeminiEnvPath ) ) {
return homeGeminiEnvPath ;
}
const homeEnvPath = path . join ( homedir ( ) , '.env' ) ;
if ( fs . existsSync ( homeEnvPath ) ) {
return homeEnvPath ;
}
return null ;
}
currentDir = parentDir ;
}
}
2026-02-03 20:16:20 +00:00
async function refreshAuthentication (
config : Config ,
adcFilePath : string | undefined ,
logPrefix : string ,
) : Promise < void > {
if ( process . env [ 'USE_CCPA' ] ) {
logger . info ( ` [ ${ logPrefix } ] Using CCPA Auth: ` ) ;
try {
if ( adcFilePath ) {
path . resolve ( adcFilePath ) ;
}
} catch ( e ) {
logger . error (
` [ ${ logPrefix } ] USE_CCPA env var is true but unable to resolve GOOGLE_APPLICATION_CREDENTIALS file path ${ adcFilePath } . Error ${ e } ` ,
) ;
}
2026-03-04 15:35:21 -05:00
const useComputeAdc = process . env [ 'GEMINI_CLI_USE_COMPUTE_ADC' ] === 'true' ;
const isHeadless = isHeadlessMode ( ) ;
const shouldSkipOauth = isHeadless || useComputeAdc ;
if ( shouldSkipOauth ) {
if ( isCloudShell ( ) || useComputeAdc ) {
logger . info (
` [ ${ logPrefix } ] Skipping LOGIN_WITH_GOOGLE due to ${ isHeadless ? 'headless mode' : 'GEMINI_CLI_USE_COMPUTE_ADC' } . Attempting COMPUTE_ADC. ` ,
) ;
try {
await config . refreshAuth ( AuthType . COMPUTE_ADC ) ;
logger . info ( ` [ ${ logPrefix } ] COMPUTE_ADC successful. ` ) ;
} catch ( adcError ) {
const adcMessage =
adcError instanceof Error ? adcError.message : String ( adcError ) ;
throw new FatalAuthenticationError (
` COMPUTE_ADC failed: ${ adcMessage } . (Skipped LOGIN_WITH_GOOGLE due to ${ isHeadless ? 'headless mode' : 'GEMINI_CLI_USE_COMPUTE_ADC' } ) ` ,
) ;
}
} else {
throw new FatalAuthenticationError (
` Interactive terminal required for LOGIN_WITH_GOOGLE. Run in an interactive terminal or set GEMINI_CLI_USE_COMPUTE_ADC=true to use Application Default Credentials. ` ,
) ;
}
} else {
try {
await config . refreshAuth ( AuthType . LOGIN_WITH_GOOGLE ) ;
} catch ( e ) {
if (
e instanceof FatalAuthenticationError &&
( isCloudShell ( ) || useComputeAdc )
) {
logger . warn (
` [ ${ logPrefix } ] LOGIN_WITH_GOOGLE failed. Attempting COMPUTE_ADC fallback. ` ,
) ;
try {
await config . refreshAuth ( AuthType . COMPUTE_ADC ) ;
logger . info ( ` [ ${ logPrefix } ] COMPUTE_ADC fallback successful. ` ) ;
} catch ( adcError ) {
logger . error (
` [ ${ logPrefix } ] COMPUTE_ADC fallback failed: ${ adcError } ` ,
) ;
const originalMessage = e instanceof Error ? e.message : String ( e ) ;
const adcMessage =
adcError instanceof Error ? adcError.message : String ( adcError ) ;
throw new FatalAuthenticationError (
` ${ originalMessage } . Fallback to COMPUTE_ADC also failed: ${ adcMessage } ` ,
) ;
}
} else {
throw e ;
}
}
}
2026-02-03 20:16:20 +00:00
logger . info (
` [ ${ logPrefix } ] GOOGLE_CLOUD_PROJECT: ${ process . env [ 'GOOGLE_CLOUD_PROJECT' ] } ` ,
) ;
} else if ( process . env [ 'GEMINI_API_KEY' ] ) {
logger . info ( ` [ ${ logPrefix } ] Using Gemini API Key ` ) ;
await config . refreshAuth ( AuthType . USE_GEMINI ) ;
} else {
const errorMessage = ` [ ${ logPrefix } ] Unable to set GeneratorConfig. Please provide a GEMINI_API_KEY or set USE_CCPA. ` ;
logger . error ( errorMessage ) ;
throw new Error ( errorMessage ) ;
}
}