mirror of
https://github.com/Ryder-Sun/Meldwork.git
synced 2026-11-07 06:56:03 -10:00
Resolve Gemini readiness from its selected native auth mode
Respect settings precedence and workspace trust when checking OAuth or API key credentials; leave other modes unverified. Checks: 107 Gemini and readiness tests; desktop packaging; git diff --check.
This commit is contained in:
1 parent
7cdc88c4d1
commit
0f2babbcf8
3 files changed
+134
No files matched your search
@@ -0,0 +1,70 @@
|
||||
const fs = require('node:fs')
|
||||
const os = require('node:os')
|
||||
const path = require('node:path')
|
||||
const JSON5 = require('json5')
|
||||
|
||||
function readSettings(file) {
|
||||
try {
|
||||
const stat = fs.statSync(file)
|
||||
if (!stat.isFile() || stat.size > 1024 * 1024) throw new Error('invalid settings')
|
||||
const value = JSON5.parse(fs.readFileSync(file, 'utf8'))
|
||||
if (!value || typeof value !== 'object' || Array.isArray(value)) throw new Error('invalid settings')
|
||||
return value
|
||||
} catch (error) {
|
||||
if (error.code === 'ENOENT') return {}
|
||||
throw error
|
||||
}
|
||||
}
|
||||
|
||||
function geminiNativeAuth(options = {}) {
|
||||
const env = options.env || process.env
|
||||
const platform = options.platform || process.platform
|
||||
const home = options.home || os.homedir()
|
||||
const base = env.GEMINI_CLI_HOME || home
|
||||
const system = env.GEMINI_CLI_SYSTEM_SETTINGS_PATH || (platform === 'darwin'
|
||||
? '/Library/Application Support/GeminiCli/settings.json' : platform === 'win32'
|
||||
? 'C:\\ProgramData\\gemini-cli\\settings.json' : '/etc/gemini-cli/settings.json')
|
||||
const defaults = env.GEMINI_CLI_SYSTEM_DEFAULTS_PATH || path.join(path.dirname(system), 'system-defaults.json')
|
||||
if (![base, system, defaults].every(value => typeof value === 'string' && path.isAbsolute(value))) return { uncertain: true }
|
||||
const root = path.join(base, '.gemini')
|
||||
try {
|
||||
const layers = [readSettings(defaults), readSettings(path.join(root, 'settings.json'))]
|
||||
const systemSettings = readSettings(system)
|
||||
const validOverrides = ['oauth-personal', 'gemini-api-key', 'vertex-ai', 'compute-default-credentials']
|
||||
const override = validOverrides.includes(env.GEMINI_CLI_AUTH_OVERRIDE) ? env.GEMINI_CLI_AUTH_OVERRIDE : ''
|
||||
let authType = override || (layers[1].security?.auth?.selectedType ?? layers[0].security?.auth?.selectedType ?? '')
|
||||
const workdir = typeof options.workdir === 'string' && path.isAbsolute(options.workdir) ? options.workdir : ''
|
||||
if (workdir && path.resolve(workdir) !== path.resolve(base)) {
|
||||
let trusted = env.GEMINI_CLI_TRUST_WORKSPACE === 'true'
|
||||
if (env.GEMINI_RESTRICTED_MODE === 'true' || env.GEMINI_CLI_TRUST_WORKSPACE === 'false') trusted = false
|
||||
else if (!trusted) {
|
||||
const enabled = systemSettings.security?.folderTrust?.enabled
|
||||
?? layers[1].security?.folderTrust?.enabled ?? layers[0].security?.folderTrust?.enabled ?? true
|
||||
if (enabled === false) trusted = true
|
||||
else {
|
||||
const trustFile = env.GEMINI_CLI_TRUSTED_FOLDERS_PATH || path.join(root, 'trustedFolders.json')
|
||||
if (!path.isAbsolute(trustFile)) return { uncertain: true }
|
||||
const rules = readSettings(trustFile)
|
||||
const real = fs.realpathSync(workdir)
|
||||
let longest = -1
|
||||
for (const [folder, level] of Object.entries(rules)) {
|
||||
if (!path.isAbsolute(folder) || !['TRUST_FOLDER', 'TRUST_PARENT', 'DO_NOT_TRUST'].includes(level)) continue
|
||||
const candidate = level === 'TRUST_PARENT' ? path.dirname(folder) : folder
|
||||
const rootPath = fs.existsSync(candidate) ? fs.realpathSync(candidate) : path.resolve(candidate)
|
||||
const relative = path.relative(rootPath, real)
|
||||
if ((relative === '' || (!relative.startsWith(`..${path.sep}`) && relative !== '..' && !path.isAbsolute(relative)))
|
||||
&& rootPath.length > longest) {
|
||||
longest = rootPath.length
|
||||
trusted = level !== 'DO_NOT_TRUST'
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if (trusted) authType = readSettings(path.join(workdir, '.gemini', 'settings.json')).security?.auth?.selectedType ?? authType
|
||||
}
|
||||
authType = systemSettings.security?.auth?.selectedType ?? authType
|
||||
return { authType: typeof authType === 'string' ? authType : '', root }
|
||||
} catch { return { uncertain: true } }
|
||||
}
|
||||
|
||||
module.exports = { geminiNativeAuth }
|
||||
@@ -12,6 +12,7 @@ const { OCR_PROVIDER_KEYS } = require('./cli/ocr-provider-environment.cjs')
|
||||
const { deepseekNativeConfig } = require('./cli/deepseek-native-config.cjs')
|
||||
const { hermesProviderFromConfig, hermesProviderEnvironment } = require('./hermes-config-summary.cjs')
|
||||
const { qwenCredentialSelection, qwenConfiguredAuth } = require('./cli/qwen-native-config.cjs')
|
||||
const { geminiNativeAuth } = require('./cli/gemini-native-config.cjs')
|
||||
const { prepareCommand, searchPath } = require('./cli/cli-discovery.cjs')
|
||||
const { isOpenClawSecretReference } = require('./cli/openclaw-runtime.cjs')
|
||||
const { NETWORK_ENV_KEYS, networkEnvironment } = require('./cli/cli-network-environment.cjs')
|
||||
@@ -803,6 +804,21 @@ function nativeCredentialState(kind, options = {}) {
|
||||
const geminiHome = env.GEMINI_CLI_HOME
|
||||
? (path.isAbsolute(env.GEMINI_CLI_HOME) ? path.join(env.GEMINI_CLI_HOME, '.gemini') : null)
|
||||
: path.join(home, '.gemini')
|
||||
if (kind === 'gemini') {
|
||||
const selection = geminiNativeAuth({ home, env, workdir: options.workdir, platform: options.platform })
|
||||
if (selection.uncertain) return { state: 'unknown', source: 'unverified' }
|
||||
if (selection.authType === 'oauth-personal') {
|
||||
return jsonContainsCredential(path.join(selection.root, 'oauth_creds.json'))
|
||||
? { state: 'ready', source: 'native-credential' }
|
||||
: { state: 'unknown', source: 'unverified' }
|
||||
}
|
||||
if (selection.authType === 'gemini-api-key') {
|
||||
const found = ['GOOGLE_API_KEY', 'GEMINI_API_KEY'].some(key => typeof env[key] === 'string' && env[key].trim())
|
||||
return found ? { state: 'ready', source: options.credentialSource === 'native-shell' ? 'native-shell' : 'native-credential' }
|
||||
: { state: 'unknown', source: 'unverified' }
|
||||
}
|
||||
if (selection.authType) return { state: 'unknown', source: 'unverified' }
|
||||
}
|
||||
const expandHome = value => String(value).replace(/^~(?=$|[\\/])/, home)
|
||||
const piHome = env.PI_CODING_AGENT_DIR ? expandHome(env.PI_CODING_AGENT_DIR) : path.join(home, '.pi', 'agent')
|
||||
const kimiHome = env.KIMI_CODE_HOME
|
||||
|
||||
@@ -0,0 +1,48 @@
|
||||
const test = require('node:test')
|
||||
const assert = require('node:assert/strict')
|
||||
const fs = require('node:fs')
|
||||
const os = require('node:os')
|
||||
const path = require('node:path')
|
||||
const { geminiNativeAuth } = require('../../../src/agents/cli/gemini-native-config.cjs')
|
||||
const { nativeCredentialState } = require('../../../src/agents/local-agent-readiness.cjs')
|
||||
|
||||
test('Gemini resolves auth through native settings and workspace trust', t => {
|
||||
const home = fs.mkdtempSync(path.join(os.tmpdir(), 'meldwork-gemini-auth-'))
|
||||
t.after(() => fs.rmSync(home, { recursive: true, force: true }))
|
||||
const root = path.join(home, '.gemini')
|
||||
const project = path.join(home, 'project')
|
||||
fs.mkdirSync(root)
|
||||
fs.mkdirSync(path.join(project, '.gemini'), { recursive: true })
|
||||
const defaults = path.join(home, 'defaults.json')
|
||||
const system = path.join(home, 'system.json')
|
||||
const env = { GEMINI_CLI_SYSTEM_SETTINGS_PATH: system, GEMINI_CLI_SYSTEM_DEFAULTS_PATH: defaults }
|
||||
const write = (file, auth) => fs.writeFileSync(file, JSON.stringify({ security: { auth: { selectedType: auth } } }))
|
||||
write(defaults, 'oauth-personal')
|
||||
write(path.join(root, 'settings.json'), 'gemini-api-key')
|
||||
write(path.join(project, '.gemini', 'settings.json'), 'vertex-ai')
|
||||
assert.equal(geminiNativeAuth({ home, env, workdir: project }).authType, 'gemini-api-key')
|
||||
fs.writeFileSync(path.join(root, 'trustedFolders.json'), JSON.stringify({ [project]: 'TRUST_FOLDER' }))
|
||||
assert.equal(geminiNativeAuth({ home, env, workdir: project }).authType, 'vertex-ai')
|
||||
assert.equal(geminiNativeAuth({ home, env: { ...env, GEMINI_RESTRICTED_MODE: 'true' }, workdir: project }).authType, 'gemini-api-key')
|
||||
assert.equal(geminiNativeAuth({ home, env: { ...env, GEMINI_CLI_AUTH_OVERRIDE: 'oauth-personal' } }).authType, 'oauth-personal')
|
||||
write(system, 'vertex-ai')
|
||||
assert.equal(geminiNativeAuth({ home, env: { ...env, GEMINI_CLI_AUTH_OVERRIDE: 'oauth-personal' }, workdir: project }).authType, 'vertex-ai')
|
||||
})
|
||||
|
||||
test('Gemini readiness checks credentials for its selected auth mode', t => {
|
||||
const home = fs.mkdtempSync(path.join(os.tmpdir(), 'meldwork-gemini-ready-'))
|
||||
t.after(() => fs.rmSync(home, { recursive: true, force: true }))
|
||||
const root = path.join(home, '.gemini')
|
||||
fs.mkdirSync(root)
|
||||
const env = { GEMINI_CLI_SYSTEM_SETTINGS_PATH: path.join(home, 'system.json'), GEMINI_CLI_AUTH_OVERRIDE: 'oauth-personal', GEMINI_API_KEY: 'unrelated' }
|
||||
assert.deepEqual(nativeCredentialState('gemini', { home, env }), { state: 'unknown', source: 'unverified' })
|
||||
fs.writeFileSync(path.join(root, 'oauth_creds.json'), JSON.stringify({ refresh_token: 'oauth-token' }))
|
||||
assert.deepEqual(nativeCredentialState('gemini', { home, env }), { state: 'ready', source: 'native-credential' })
|
||||
env.GEMINI_CLI_AUTH_OVERRIDE = 'gemini-api-key'
|
||||
assert.deepEqual(nativeCredentialState('gemini', { home, env }), { state: 'ready', source: 'native-credential' })
|
||||
env.GEMINI_CLI_AUTH_OVERRIDE = 'vertex-ai'
|
||||
assert.deepEqual(nativeCredentialState('gemini', { home, env }), { state: 'unknown', source: 'unverified' })
|
||||
env.GEMINI_CLI_AUTH_OVERRIDE = 'gemini-api-key'
|
||||
delete env.GEMINI_API_KEY
|
||||
assert.deepEqual(nativeCredentialState('gemini', { home, env }), { state: 'unknown', source: 'unverified' })
|
||||
})
|
||||
Reference in new issue
Block a user