Merge pull request #680 from 0xble/fix/image-analyzer-thinking-blocks

fix(hooks): handle thinking blocks in image analyzer response parsing
This commit is contained in:
Kai (Tam Nhu) Tran authored and GitHub committed 2026-03-07 02:58:39 -05:00
commit 3c7b352656
2 files changed
+254 -20

No files matched your search

+51 -20
View File
@@ -12,7 +12,7 @@
* CCS_CURRENT_PROVIDER - Current CLIProxy provider (e.g., agy, gemini, codex)
* CCS_IMAGE_ANALYSIS_TIMEOUT=60 - Timeout in seconds (default: 60)
* CCS_PROFILE_TYPE - Profile type (account/default skip)
* ANTHROPIC_MODEL - Fallback model if provider not in mapping
* ANTHROPIC_MODEL - Chat model env (not used for image analysis fallback)
* CCS_DEBUG=1 - Enable debug output
*
* Exit codes:
@@ -147,6 +147,46 @@ function parseProviderModels(envValue) {
return result;
}
/**
* Extract concatenated text content from CLIProxy response blocks.
* Skips thinking and other non-text blocks.
*/
function extractTextContent(response) {
if (!response || !Array.isArray(response.content)) {
return null;
}
const textBlocks = response.content
.filter((block) => block && block.type === 'text' && typeof block.text === 'string')
.map((block) => block.text)
.filter((text) => text.trim());
if (textBlocks.length === 0) {
return null;
}
return textBlocks.join('\n\n');
}
/**
* Parse raw CLIProxy response body and extract text content.
*/
function parseCliProxyResponse(data) {
let response;
try {
response = JSON.parse(data);
} catch (err) {
throw new Error(`Failed to parse response: ${err.message}`);
}
const text = extractTextContent(response);
if (!text) {
throw new Error('No text content in response');
}
return text;
}
/**
* Get model for current provider from provider_models mapping
* Returns primary model only (for display/logging)
@@ -160,13 +200,14 @@ function getModelForProvider() {
/**
* Get list of models to try in order:
* 1. provider_models[current_provider] (if exists)
* 2. DEFAULT_MODEL
* 3. ANTHROPIC_MODEL from profile (if different and exists)
* 2. DEFAULT_MODEL when no provider-specific vision model is configured
*
* ANTHROPIC_MODEL is intentionally ignored here because the chat model may
* not be vision-capable on the current provider route.
*/
function getModelsToTry() {
const currentProvider = process.env.CCS_CURRENT_PROVIDER || '';
const providerModels = parseProviderModels(process.env.CCS_IMAGE_ANALYSIS_PROVIDER_MODELS);
const anthropicModel = process.env.ANTHROPIC_MODEL;
const models = [];
const seen = new Set();
@@ -177,17 +218,14 @@ function getModelsToTry() {
seen.add(providerModels[currentProvider]);
}
// 2. Default model
if (!seen.has(DEFAULT_MODEL)) {
// 2. Default model — only when no provider-specific model is configured,
// since DEFAULT_MODEL (gemini-2.5-flash) may not be routable on the
// current provider's CLIProxy endpoint (e.g. codex, claude)
if (models.length === 0 && !seen.has(DEFAULT_MODEL)) {
models.push(DEFAULT_MODEL);
seen.add(DEFAULT_MODEL);
}
// 3. ANTHROPIC_MODEL fallback
if (anthropicModel && !seen.has(anthropicModel)) {
models.push(anthropicModel);
}
return models;
}
@@ -381,17 +419,10 @@ function analyzeViaCliProxy(base64Data, mediaType, model, timeoutMs) {
}
try {
const response = JSON.parse(data);
const text = response.content?.[0]?.text;
if (!text) {
reject(new Error('No text content in response'));
return;
}
const text = parseCliProxyResponse(data);
resolve(text);
} catch (err) {
reject(new Error(`Failed to parse response: ${err.message}`));
reject(err);
}
});
}