From e090c8b0d4e65eda3c9b4dcdd4ef0521c96a68b9 Mon Sep 17 00:00:00 2001 From: Tam Nhu Tran Date: Fri, 10 Apr 2026 17:40:27 -0400 Subject: [PATCH] fix(image-analysis): preserve default runtime deps --- docs/project-roadmap.md | 2 +- .../hooks/image-analysis-runtime-status.ts | 13 +++-- .../image-analysis-runtime-status.test.ts | 49 +++++++++++++------ 3 files changed, 44 insertions(+), 20 deletions(-) diff --git a/docs/project-roadmap.md b/docs/project-roadmap.md index 7a4ebde8..b25ed9af 100644 --- a/docs/project-roadmap.md +++ b/docs/project-roadmap.md @@ -41,7 +41,7 @@ All major modularization work is complete. The codebase evolved from monolithic ### Recent Fixes -- **2026-04-10**: **#944** Image Analysis auth readiness no longer collapses to native Read when a partial runtime-status dependency override omits `initializeAccounts`. CCS now treats account-registry warm-up as optional in that path, still reads token-backed auth status directly, and includes regression coverage for the missing-initializer case so Kimi-backed sessions do not fail on `deps.initializeAccounts is not a function`. +- **2026-04-10**: **#944** Image Analysis auth readiness no longer collapses to native Read when merged runtime-status dependency overrides include a missing initializer value. CCS now preserves default dependency functions when override entries are `undefined`, still reads token-backed auth status directly in the local readiness path, and includes regression coverage for the missing-initializer case that previously surfaced as `deps.initializeAccounts is not a function`. - **2026-04-10**: **#945** CCS now normalizes Gemini CLI and Antigravity tier signals around an explicit `free / pro / ultra / unknown` model, preserves raw tier ids such as `g1-pro-tier`, enriches Gemini quota responses with provider entitlement evidence, classifies `MODEL_CAPACITY_EXHAUSTED` separately from auth/entitlement failures, fixes the Antigravity CLI quota table so live quota-derived tiers no longer collapse back to stale `unknown`, adds Gemini tier ids to CLI quota output, extends Gemini Flash Lite grouping to cover `gemini-3.1-flash-lite-preview`, and allows Gemini account surfaces to render the same tier badge semantics as Antigravity. - **2026-04-09**: **#938** Cliproxy model routing now exposes backend-pinned short prefixes for overlapping OAuth backends. CCS repairs managed OAuth auth-file prefixes for Gemini CLI (`gcli`) and Antigravity (`agy`), enriches `/api/cliproxy/catalog` with routing hints that show whether an unprefixed model is safe, shadowed, or prefix-only, upgrades `ccs cliproxy catalog` plus interactive variant model pickers to surface the pinned names, and updates the `ccs config` Cliproxy model selection UI so users can see the preferred call name and current effective backend before saving settings. - **2026-04-08**: **#931** `/cliproxy` model pickers now source their provider catalogs from CLIProxy management model definitions instead of treating the UI catalog file as the dropdown source of truth. CCS now refreshes live model definitions for Gemini, Codex, Claude, Antigravity, Qwen, iFlow, Kiro, GitHub Copilot, and Kimi through `/api/cliproxy/catalog`, overlays CCS-only preset/default metadata on top of those upstream models, keeps `/api/cliproxy/models` as the live availability feed, and falls back to cached/static catalogs when the proxy is unavailable so the dashboard never goes blank. diff --git a/src/utils/hooks/image-analysis-runtime-status.ts b/src/utils/hooks/image-analysis-runtime-status.ts index 8d40532f..4322b9e5 100644 --- a/src/utils/hooks/image-analysis-runtime-status.ts +++ b/src/utils/hooks/image-analysis-runtime-status.ts @@ -37,6 +37,13 @@ const defaultDeps: ImageAnalysisRuntimeStatusDeps = { isCliproxyRunning: () => isCliproxyRunning(), }; +function mergeDefinedDeps(defaults: T, overrides: Partial): T { + const definedOverrides = Object.fromEntries( + Object.entries(overrides as Record).filter(([, value]) => value !== undefined) + ) as Partial; + return { ...defaults, ...definedOverrides }; +} + async function resolveAuthReadiness( status: ImageAnalysisStatus, deps: ImageAnalysisRuntimeStatusDeps @@ -64,9 +71,7 @@ async function resolveAuthReadiness( (entry) => entry.provider === authProvider && entry.authenticated ); } else { - if (typeof deps.initializeAccounts === 'function') { - deps.initializeAccounts(); - } + deps.initializeAccounts(); authenticated = deps.getAuthStatus(authProvider).authenticated; } @@ -162,7 +167,7 @@ export async function hydrateImageAnalysisRuntimeStatus( baseStatus: ImageAnalysisStatus, deps: Partial = {} ): Promise { - const resolvedDeps = { ...defaultDeps, ...deps }; + const resolvedDeps = mergeDefinedDeps(defaultDeps, deps); const authStatus = await resolveAuthReadiness(baseStatus, resolvedDeps); const proxyStatus = await resolveProxyReadiness(baseStatus, resolvedDeps); const mergedStatus = { diff --git a/tests/unit/utils/hooks/image-analysis-runtime-status.test.ts b/tests/unit/utils/hooks/image-analysis-runtime-status.test.ts index d314eb7a..e94d34cc 100644 --- a/tests/unit/utils/hooks/image-analysis-runtime-status.test.ts +++ b/tests/unit/utils/hooks/image-analysis-runtime-status.test.ts @@ -1,3 +1,6 @@ +import { mkdtempSync, rmSync } from 'fs'; +import { tmpdir } from 'os'; +import { join } from 'path'; import { describe, expect, it } from 'bun:test'; import { hydrateImageAnalysisRuntimeStatus } from '../../../../src/utils/hooks/image-analysis-runtime-status'; import type { ImageAnalysisStatus } from '../../../../src/utils/hooks/image-analysis-backend-resolver'; @@ -89,22 +92,38 @@ describe('image-analysis-runtime-status', () => { }); it('uses getAuthStatus when initializeAccounts is omitted from an override', async () => { - const status = await hydrateImageAnalysisRuntimeStatus(createStatus(), { - getProxyTarget: () => ({ host: '127.0.0.1', port: 8317, protocol: 'http', isRemote: false }), - initializeAccounts: undefined as unknown as () => void, - getAuthStatus: () => ({ - provider: 'ghcp', - authenticated: true, - tokenDir: '/tmp/auth', - tokenFiles: ['github-copilot-test.json'], - accounts: [], - defaultAccount: undefined, - }), - isCliproxyRunning: async () => true, - }); + const tempCcsHome = mkdtempSync(join(tmpdir(), 'ccs-image-analysis-runtime-status-')); + const originalCcsHome = process.env.CCS_HOME; + const originalCcsDir = process.env.CCS_DIR; + delete process.env.CCS_DIR; + process.env.CCS_HOME = tempCcsHome; - expect(status.authReadiness).toBe('ready'); - expect(status.effectiveRuntimeMode).toBe('cliproxy-image-analysis'); + try { + const status = await hydrateImageAnalysisRuntimeStatus(createStatus(), { + getProxyTarget: () => ({ host: '127.0.0.1', port: 8317, protocol: 'http', isRemote: false }), + initializeAccounts: undefined as unknown as () => void, + getAuthStatus: () => ({ + provider: 'ghcp', + authenticated: true, + tokenDir: '/tmp/auth', + tokenFiles: ['github-copilot-test.json'], + accounts: [], + defaultAccount: undefined, + }), + isCliproxyRunning: async () => true, + }); + + expect(status.authReadiness).toBe('ready'); + expect(status.effectiveRuntimeMode).toBe('cliproxy-image-analysis'); + } finally { + if (originalCcsHome === undefined) delete process.env.CCS_HOME; + else process.env.CCS_HOME = originalCcsHome; + + if (originalCcsDir === undefined) delete process.env.CCS_DIR; + else process.env.CCS_DIR = originalCcsDir; + + rmSync(tempCcsHome, { recursive: true, force: true }); + } }); it('treats an unreachable remote proxy as unavailable', async () => {