fix(image-analysis): preview backend status safely

This commit is contained in:
Tam Nhu Tran committed 2026-04-01 15:32:37 -04:00
1 parent 665668579d
commit d394772f7c
10 files changed
+507 -26

No files matched your search

+44 -1
View File
@@ -1013,12 +1013,55 @@ async function main(): Promise<void> {
}
const webSearchEnv = getWebSearchHookEnv();
const imageAnalysisEnv = getImageAnalysisHookEnv({
let imageAnalysisEnv = getImageAnalysisHookEnv({
profileName: profileInfo.name,
profileType: profileInfo.type,
settings,
cliproxyBridge,
});
const imageAnalysisProvider = imageAnalysisEnv['CCS_CURRENT_PROVIDER'];
if (
resolvedTarget === 'claude' &&
imageAnalysisEnv['CCS_IMAGE_ANALYSIS_SKIP'] !== '1' &&
imageAnalysisProvider
) {
const verboseProxyLaunch =
remainingArgs.includes('--verbose') ||
remainingArgs.includes('-v') ||
targetRemainingArgs.includes('--verbose') ||
targetRemainingArgs.includes('-v');
if (!isAuthenticated(imageAnalysisProvider as CLIProxyProvider)) {
console.error(
info(
`Image analysis via ${imageAnalysisProvider} is configured, but CLIProxy auth is missing. This session will use native Read. Run "ccs ${imageAnalysisProvider} --auth" to enable it.`
)
);
imageAnalysisEnv = {
...imageAnalysisEnv,
CCS_CURRENT_PROVIDER: '',
CCS_IMAGE_ANALYSIS_SKIP: '1',
};
} else {
const ensureServiceResult = await ensureCliproxyService(
CLIPROXY_DEFAULT_PORT,
verboseProxyLaunch
);
if (!ensureServiceResult.started) {
console.error(
warn(
`Image analysis via ${imageAnalysisProvider} is unavailable because CCS could not start the local CLIProxy service. This session will use native Read.`
)
);
imageAnalysisEnv = {
...imageAnalysisEnv,
CCS_CURRENT_PROVIDER: '',
CCS_IMAGE_ANALYSIS_SKIP: '1',
};
}
}
}
// Get global env vars (DISABLE_TELEMETRY, etc.) for third-party profiles
const globalEnvConfig = getGlobalEnvConfig();
const globalEnv = globalEnvConfig.enabled ? globalEnvConfig.env : {};
@@ -292,6 +292,17 @@ function resolveBackend(
};
}
const hasDirectAnthropicApiKey = Boolean(settings?.env?.ANTHROPIC_API_KEY?.trim());
const hasBaseUrl = Boolean(settings?.env?.ANTHROPIC_BASE_URL?.trim());
if (hasDirectAnthropicApiKey && !hasBaseUrl) {
return {
backendId: null,
backendDisplayName: null,
resolutionSource: 'unresolved',
reason: 'Direct Anthropic settings profiles use native file access unless explicitly mapped.',
};
}
const fallbackBackend = normalizeImageAnalysisBackendId(
config.fallback_backend,
Object.keys(config.provider_models)
@@ -323,7 +334,10 @@ export function resolveImageAnalysisStatus(
? (config.provider_models[resolution.backendId] ?? null)
: null;
const shouldPersistHook =
config.enabled && context.profileType !== 'default' && context.profileType !== 'account';
config.enabled &&
context.profileType !== 'default' &&
context.profileType !== 'account' &&
Boolean(resolution.backendId && model);
let status: ImageAnalysisStatusCode = 'active';
let reason = resolution.reason;
@@ -353,8 +367,16 @@ export function resolveImageAnalysisStatus(
(!context.cliproxyBridge.usesCurrentTarget || !context.cliproxyBridge.usesCurrentAuthToken)
) {
status = 'attention';
reason =
'Active, but runtime uses the current CLIProxy target instead of the stale route saved in this profile.';
if (!context.cliproxyBridge.usesCurrentTarget && !context.cliproxyBridge.usesCurrentAuthToken) {
reason =
'Runtime uses the current CLIProxy route and auth token instead of the saved values in this profile.';
} else if (!context.cliproxyBridge.usesCurrentTarget) {
reason =
'Runtime uses the current CLIProxy route instead of the saved route in this profile.';
} else {
reason =
'Runtime uses the current CLIProxy auth token instead of the saved token in this profile.';
}
} else if (resolution.resolutionSource === 'profile-backend') {
status = 'mapped';
}
+30
View File
@@ -303,6 +303,13 @@ function resolveImageAnalysisStatusForProfile(
};
}
function resolvePreviewImageAnalysisStatus(profileOrVariant: string, settings: Settings) {
const normalizedSettings = canonicalizeProfileSettings(profileOrVariant, settings);
const settingsPath = resolveSettingsPath(profileOrVariant);
return resolveImageAnalysisStatusForProfile(profileOrVariant, normalizedSettings, settingsPath);
}
function writeSettingsAtomically(settingsPath: string, settings: Settings): void {
const tempPath = `${settingsPath}.tmp.${process.pid}`;
fs.writeFileSync(tempPath, JSON.stringify(settings, null, 2) + '\n');
@@ -428,6 +435,29 @@ router.get('/:profile/raw', (req: Request, res: Response): void => {
}
});
/**
* POST /api/settings/:profile/image-analysis-status - Preview image analysis status from editor JSON
*/
router.post('/:profile/image-analysis-status', (req: Request, res: Response): void => {
if (!requireSensitiveLocalAccess(req, res)) return;
try {
const { profile } = req.params;
const { settings } = req.body;
if (!settings || typeof settings !== 'object') {
res.status(400).json({ error: 'settings object is required in request body' });
return;
}
res.json({
imageAnalysisStatus: resolvePreviewImageAnalysisStatus(profile, settings as Settings),
});
} catch (error) {
respondInternalError(res, error, 'Internal server error.');
}
});
/** Required env vars for CLIProxy providers to function */
const REQUIRED_ENV_KEYS = ['ANTHROPIC_BASE_URL', 'ANTHROPIC_AUTH_TOKEN'] as const;