mirror of
https://github.com/tiennm99/ccs.git
synced 2026-10-14 06:17:27 +00:00
fix(image-analysis): surface runtime readiness
This commit is contained in:
1 parent
d394772f7c
commit
1a01c6fc68
12 files changed
+788
-123
No files matched your search
@@ -33,6 +33,16 @@ export type ImageAnalysisStatusCode =
|
||||
| 'skipped'
|
||||
| 'hook-missing';
|
||||
|
||||
export type ImageAnalysisAuthReadiness = 'not-needed' | 'ready' | 'missing' | 'unknown';
|
||||
export type ImageAnalysisProxyReadiness =
|
||||
| 'not-needed'
|
||||
| 'ready'
|
||||
| 'remote'
|
||||
| 'stopped'
|
||||
| 'unavailable'
|
||||
| 'unknown';
|
||||
export type ImageAnalysisEffectiveRuntimeMode = 'cliproxy-image-analysis' | 'native-read';
|
||||
|
||||
export interface ImageAnalysisResolutionContext {
|
||||
profileName: string;
|
||||
profileType?: ProfileType;
|
||||
@@ -61,6 +71,14 @@ export interface ImageAnalysisStatus {
|
||||
usesCurrentAuthToken: boolean | null;
|
||||
hookInstalled: boolean | null;
|
||||
sharedHookInstalled: boolean | null;
|
||||
authReadiness: ImageAnalysisAuthReadiness;
|
||||
authProvider: string | null;
|
||||
authDisplayName: string | null;
|
||||
authReason: string | null;
|
||||
proxyReadiness: ImageAnalysisProxyReadiness;
|
||||
proxyReason: string | null;
|
||||
effectiveRuntimeMode: ImageAnalysisEffectiveRuntimeMode;
|
||||
effectiveRuntimeReason: string | null;
|
||||
}
|
||||
|
||||
function resolveProviderFromBaseUrl(baseUrl: unknown): string | null {
|
||||
@@ -397,5 +415,34 @@ export function resolveImageAnalysisStatus(
|
||||
usesCurrentAuthToken: context.cliproxyBridge?.usesCurrentAuthToken ?? null,
|
||||
hookInstalled: context.hookInstalled ?? null,
|
||||
sharedHookInstalled: context.sharedHookInstalled ?? null,
|
||||
authReadiness:
|
||||
resolution.backendId && model && isCLIProxyProvider(resolution.backendId)
|
||||
? 'unknown'
|
||||
: 'not-needed',
|
||||
authProvider:
|
||||
resolution.backendId && isCLIProxyProvider(resolution.backendId)
|
||||
? resolution.backendId
|
||||
: null,
|
||||
authDisplayName:
|
||||
resolution.backendId && isCLIProxyProvider(resolution.backendId)
|
||||
? getProviderDisplayName(resolution.backendId)
|
||||
: null,
|
||||
authReason:
|
||||
resolution.backendId && model && isCLIProxyProvider(resolution.backendId)
|
||||
? 'Auth readiness has not been verified yet.'
|
||||
: null,
|
||||
proxyReadiness: resolution.backendId && model ? 'unknown' : 'not-needed',
|
||||
proxyReason:
|
||||
resolution.backendId && model
|
||||
? 'CLIProxy runtime readiness has not been verified yet.'
|
||||
: null,
|
||||
effectiveRuntimeMode:
|
||||
config.enabled && resolution.backendId && model && status !== 'hook-missing'
|
||||
? 'cliproxy-image-analysis'
|
||||
: 'native-read',
|
||||
effectiveRuntimeReason:
|
||||
status === 'hook-missing' || !config.enabled || !resolution.backendId || !model
|
||||
? reason
|
||||
: null,
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,175 @@
|
||||
import { getAuthStatus, initializeAccounts, type AuthStatus } from '../../cliproxy/auth-handler';
|
||||
import { fetchRemoteAuthStatus, type RemoteAuthStatus } from '../../cliproxy/remote-auth-fetcher';
|
||||
import { getProxyTarget, type ProxyTarget } from '../../cliproxy/proxy-target-resolver';
|
||||
import { getProviderDisplayName, isCLIProxyProvider } from '../../cliproxy/provider-capabilities';
|
||||
import { isCliproxyRunning } from '../../cliproxy/stats-fetcher';
|
||||
import type { CLIProxyProvider } from '../../cliproxy/types';
|
||||
import {
|
||||
DEFAULT_IMAGE_ANALYSIS_CONFIG,
|
||||
type ImageAnalysisConfig,
|
||||
} from '../../config/unified-config-types';
|
||||
import {
|
||||
resolveImageAnalysisStatus,
|
||||
type ImageAnalysisResolutionContext,
|
||||
type ImageAnalysisStatus,
|
||||
} from './image-analysis-backend-resolver';
|
||||
|
||||
interface ImageAnalysisRuntimeStatusDeps {
|
||||
fetchRemoteAuthStatus: (target: ProxyTarget) => Promise<RemoteAuthStatus[]>;
|
||||
getAuthStatus: (provider: CLIProxyProvider) => AuthStatus;
|
||||
getProxyTarget: () => ProxyTarget;
|
||||
initializeAccounts: () => void;
|
||||
isCliproxyRunning: () => Promise<boolean>;
|
||||
}
|
||||
|
||||
const defaultDeps: ImageAnalysisRuntimeStatusDeps = {
|
||||
fetchRemoteAuthStatus,
|
||||
getAuthStatus,
|
||||
getProxyTarget,
|
||||
initializeAccounts,
|
||||
isCliproxyRunning: () => isCliproxyRunning(),
|
||||
};
|
||||
|
||||
async function resolveAuthReadiness(
|
||||
status: ImageAnalysisStatus,
|
||||
deps: ImageAnalysisRuntimeStatusDeps
|
||||
): Promise<
|
||||
Pick<ImageAnalysisStatus, 'authReadiness' | 'authProvider' | 'authDisplayName' | 'authReason'>
|
||||
> {
|
||||
if (!status.backendId || !status.model || !isCLIProxyProvider(status.backendId)) {
|
||||
return {
|
||||
authReadiness: 'not-needed',
|
||||
authProvider: null,
|
||||
authDisplayName: null,
|
||||
authReason: null,
|
||||
};
|
||||
}
|
||||
|
||||
const authProvider = status.backendId;
|
||||
const authDisplayName = getProviderDisplayName(authProvider);
|
||||
|
||||
try {
|
||||
let authenticated = false;
|
||||
const target = deps.getProxyTarget();
|
||||
if (target.isRemote) {
|
||||
const remoteStatuses = await deps.fetchRemoteAuthStatus(target);
|
||||
authenticated = remoteStatuses.some(
|
||||
(entry) => entry.provider === authProvider && entry.authenticated
|
||||
);
|
||||
} else {
|
||||
deps.initializeAccounts();
|
||||
authenticated = deps.getAuthStatus(authProvider).authenticated;
|
||||
}
|
||||
|
||||
return {
|
||||
authReadiness: authenticated ? 'ready' : 'missing',
|
||||
authProvider,
|
||||
authDisplayName,
|
||||
authReason: authenticated
|
||||
? null
|
||||
: `${authDisplayName} auth is missing. Run "ccs ${authProvider} --auth" to enable image analysis.`,
|
||||
};
|
||||
} catch (error) {
|
||||
return {
|
||||
authReadiness: 'unknown',
|
||||
authProvider,
|
||||
authDisplayName,
|
||||
authReason: `CCS could not verify ${authDisplayName} auth readiness: ${(error as Error).message}`,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
async function resolveProxyReadiness(
|
||||
status: ImageAnalysisStatus,
|
||||
deps: ImageAnalysisRuntimeStatusDeps
|
||||
): Promise<Pick<ImageAnalysisStatus, 'proxyReadiness' | 'proxyReason'>> {
|
||||
if (!status.backendId || !status.model) {
|
||||
return {
|
||||
proxyReadiness: 'not-needed',
|
||||
proxyReason: null,
|
||||
};
|
||||
}
|
||||
|
||||
const target = deps.getProxyTarget();
|
||||
const reachable = await deps.isCliproxyRunning();
|
||||
if (target.isRemote) {
|
||||
return {
|
||||
proxyReadiness: reachable ? 'remote' : 'unavailable',
|
||||
proxyReason: reachable
|
||||
? `Remote CLIProxy target ${target.host}:${target.port} is reachable.`
|
||||
: `Remote CLIProxy target ${target.host}:${target.port} is unreachable.`,
|
||||
};
|
||||
}
|
||||
|
||||
return {
|
||||
proxyReadiness: reachable ? 'ready' : 'stopped',
|
||||
proxyReason: reachable
|
||||
? 'Local CLIProxy service is reachable.'
|
||||
: 'Local CLIProxy service is idle. CCS will start it automatically when image analysis is needed.',
|
||||
};
|
||||
}
|
||||
|
||||
function resolveEffectiveRuntime(
|
||||
status: ImageAnalysisStatus
|
||||
): Pick<ImageAnalysisStatus, 'effectiveRuntimeMode' | 'effectiveRuntimeReason'> {
|
||||
if (!status.enabled || !status.backendId || !status.model) {
|
||||
return {
|
||||
effectiveRuntimeMode: 'native-read',
|
||||
effectiveRuntimeReason: status.reason,
|
||||
};
|
||||
}
|
||||
|
||||
if (status.status === 'hook-missing') {
|
||||
return {
|
||||
effectiveRuntimeMode: 'native-read',
|
||||
effectiveRuntimeReason: status.reason,
|
||||
};
|
||||
}
|
||||
|
||||
if (status.authReadiness === 'missing' || status.authReadiness === 'unknown') {
|
||||
return {
|
||||
effectiveRuntimeMode: 'native-read',
|
||||
effectiveRuntimeReason: status.authReason,
|
||||
};
|
||||
}
|
||||
|
||||
if (status.proxyReadiness === 'unavailable' || status.proxyReadiness === 'unknown') {
|
||||
return {
|
||||
effectiveRuntimeMode: 'native-read',
|
||||
effectiveRuntimeReason: status.proxyReason,
|
||||
};
|
||||
}
|
||||
|
||||
return {
|
||||
effectiveRuntimeMode: 'cliproxy-image-analysis',
|
||||
effectiveRuntimeReason: status.status === 'attention' ? status.reason : null,
|
||||
};
|
||||
}
|
||||
|
||||
export async function hydrateImageAnalysisRuntimeStatus(
|
||||
baseStatus: ImageAnalysisStatus,
|
||||
deps: Partial<ImageAnalysisRuntimeStatusDeps> = {}
|
||||
): Promise<ImageAnalysisStatus> {
|
||||
const resolvedDeps = { ...defaultDeps, ...deps };
|
||||
const authStatus = await resolveAuthReadiness(baseStatus, resolvedDeps);
|
||||
const proxyStatus = await resolveProxyReadiness(baseStatus, resolvedDeps);
|
||||
const mergedStatus = {
|
||||
...baseStatus,
|
||||
...authStatus,
|
||||
...proxyStatus,
|
||||
};
|
||||
|
||||
return {
|
||||
...mergedStatus,
|
||||
...resolveEffectiveRuntime(mergedStatus),
|
||||
};
|
||||
}
|
||||
|
||||
export async function resolveImageAnalysisRuntimeStatus(
|
||||
context: ImageAnalysisResolutionContext,
|
||||
config: ImageAnalysisConfig = DEFAULT_IMAGE_ANALYSIS_CONFIG,
|
||||
deps: Partial<ImageAnalysisRuntimeStatusDeps> = {}
|
||||
): Promise<ImageAnalysisStatus> {
|
||||
const baseStatus = resolveImageAnalysisStatus(context, config);
|
||||
return hydrateImageAnalysisRuntimeStatus(baseStatus, deps);
|
||||
}
|
||||
@@ -14,6 +14,10 @@ export {
|
||||
type ImageAnalysisResolutionContext,
|
||||
type ImageAnalysisStatus,
|
||||
} from './image-analysis-backend-resolver';
|
||||
export {
|
||||
hydrateImageAnalysisRuntimeStatus,
|
||||
resolveImageAnalysisRuntimeStatus,
|
||||
} from './image-analysis-runtime-status';
|
||||
export {
|
||||
getImageAnalyzerHookPath,
|
||||
getImageAnalyzerHookConfig,
|
||||
|
||||
Reference in new issue
Block a user