feat(core): migrate console calls to debugLogger (M3 Phase 6)

Migrate remaining console.* calls in packages/core utilities and services
to use the structured DebugLogger pattern. This continues the Phase 6
migration work for utilities and helpers.

Files migrated:
- config/config.ts, core/client.ts, core/prompts.ts
- core/anthropicContentGenerator, core/openaiContentGenerator
- core/coreToolScheduler.ts, fallback/handler.ts
- models/modelRegistry.ts, prompts/prompt-registry.ts
- services/chatRecordingService.ts, sessionService.ts, loopDetectionService.ts
- skills/skill-load.ts, skill-manager.ts
- subagents/subagent.ts, subagent-manager.ts
- utils: editor, environmentContext, fileUtils, getFolderStructure,
  installationManager, jsonl-utils, llm-edit-fixer, nextSpeakerChecker,
  openaiLogger, request-tokenizer/*, retry, ripgrepUtils, safeJsonParse,
  summarizer, systemEncoding, workspaceContext

Remaining: utils/errorReporting.ts (13 calls)
This commit is contained in:
tanzhenxin 2026-01-25 22:12:45 +08:00
parent 78f6bd4d88
commit 45df0e8b82
34 changed files with 186 additions and 92 deletions

View file

@ -1584,10 +1584,9 @@ export class Config {
if (!toolName) {
// Log warning and skip this tool instead of crashing
console.warn(
`[Config] Skipping tool registration: ${className} is missing static Name property. ` +
`Tools must define a static Name property to be registered. ` +
`Location: config.ts:registerCoreTool`,
this.debugLogger.warn(
`Skipping tool registration: ${className} is missing static Name property. ` +
`Tools must define a static Name property to be registered.`,
);
return;
}
@ -1596,8 +1595,8 @@ export class Config {
try {
registry.registerTool(new ToolClass(...args));
} catch (error) {
console.error(
`[Config] Failed to register tool ${className} (${toolName}):`,
this.debugLogger.error(
`Failed to register tool ${className} (${toolName}):`,
error,
);
throw error; // Re-throw after logging context

View file

@ -29,6 +29,9 @@ import { RequestTokenEstimator } from '../../utils/request-tokenizer/index.js';
import { safeJsonParse } from '../../utils/safeJsonParse.js';
import { AnthropicContentConverter } from './converter.js';
import { buildRuntimeFetchOptions } from '../../utils/runtimeFetchOptions.js';
import { createDebugLogger } from '../../utils/debugLogger.js';
const debugLogger = createDebugLogger('ANTHROPIC');
type StreamingBlockState = {
type: string;
@ -117,7 +120,7 @@ export class AnthropicContentGenerator implements ContentGenerator {
totalTokens: result.totalTokens,
};
} catch (error) {
console.warn(
debugLogger.warn(
'Failed to calculate tokens with tokenizer, ' +
'falling back to simple method:',
error,

View file

@ -15,6 +15,9 @@ import type {
// Config
import { ApprovalMode, type Config } from '../config/config.js';
import { createDebugLogger } from '../utils/debugLogger.js';
const debugLogger = createDebugLogger('CLIENT');
// Core modules
import type { ContentGenerator } from './contentGenerator.js';
@ -262,9 +265,7 @@ export class GeminiClient {
contextLines.join('\n'),
];
if (this.config.getDebugMode()) {
console.log(contextParts.join('\n'));
}
debugLogger.debug(contextParts.join('\n'));
return {
contextParts,
newIdeContext: currentIdeContext,
@ -394,9 +395,7 @@ export class GeminiClient {
changeLines.join('\n'),
];
if (this.config.getDebugMode()) {
console.log(contextParts.join('\n'));
}
debugLogger.debug(contextParts.join('\n'));
return {
contextParts,
newIdeContext: currentIdeContext,

View file

@ -18,6 +18,9 @@ import type {
AnyToolInvocation,
ChatRecordingService,
} from '../index.js';
import { createDebugLogger } from '../utils/debugLogger.js';
const debugLogger = createDebugLogger('TOOL_SCHEDULER');
import {
ToolConfirmationOutcome,
ApprovalMode,
@ -1301,7 +1304,7 @@ export class CoreToolScheduler {
this.setStatusInternal(pendingTool.request.callId, 'scheduled');
}
} catch (error) {
console.error(
debugLogger.error(
`Error checking confirmation for tool ${pendingTool.request.callId}:`,
error,
);

View file

@ -5,6 +5,9 @@
*/
import type { GenerateContentParameters } from '@google/genai';
import { createDebugLogger } from '../../utils/debugLogger.js';
const debugLogger = createDebugLogger('OPENAI_ERROR');
export interface RequestContext {
userPromptId: string;
@ -48,7 +51,7 @@ export class EnhancedErrorHandler implements ErrorHandler {
const logPrefix = context.isStreaming
? 'OpenAI API Streaming Error:'
: 'OpenAI API Error:';
console.error(logPrefix, errorMessage);
debugLogger.error(logPrefix, errorMessage);
}
// Provide helpful timeout-specific error message

View file

@ -15,6 +15,9 @@ import { EnhancedErrorHandler } from './errorHandler.js';
import { RequestTokenEstimator } from '../../utils/request-tokenizer/index.js';
import type { ContentGeneratorConfig } from '../contentGenerator.js';
import { isAbortError } from '../../utils/errors.js';
import { createDebugLogger } from '../../utils/debugLogger.js';
const debugLogger = createDebugLogger('OPENAI');
export class OpenAIContentGenerator implements ContentGenerator {
protected pipeline: ContentGenerationPipeline;
@ -88,7 +91,7 @@ export class OpenAIContentGenerator implements ContentGenerator {
totalTokens: result.totalTokens,
};
} catch (error) {
console.warn(
debugLogger.warn(
'Failed to calculate tokens with new tokenizer, falling back to simple method:',
error,
);
@ -152,7 +155,7 @@ export class OpenAIContentGenerator implements ContentGenerator {
],
};
} catch (error) {
console.error('OpenAI API Embedding Error:', error);
debugLogger.error('OpenAI API Embedding Error:', error);
throw new Error(
`OpenAI API error: ${error instanceof Error ? error.message : String(error)}`,
);

View file

@ -12,6 +12,9 @@ import process from 'node:process';
import { isGitRepository } from '../utils/gitUtils.js';
import { QWEN_CONFIG_DIR } from '../tools/memoryTool.js';
import type { GenerateContentConfig } from '@google/genai';
import { createDebugLogger } from '../utils/debugLogger.js';
const debugLogger = createDebugLogger('PROMPTS');
export function resolvePathFromEnv(envVar?: string): {
isSwitch: boolean;
@ -46,7 +49,7 @@ export function resolvePathFromEnv(envVar?: string): {
}
} catch (error) {
// If os.homedir() fails, we catch the error instead of crashing.
console.warn(
debugLogger.warn(
`Could not resolve home directory for path: ${trimmedEnvVar}`,
error,
);
@ -774,7 +777,7 @@ function getToolCallExamples(model?: string): string {
case 'general':
return generalToolCallExamples;
default:
console.warn(
debugLogger.warn(
`Unknown QWEN_CODE_TOOL_CALL_STYLE value: ${toolCallStyle}. Using model-based detection.`,
);
break;

View file

@ -6,6 +6,9 @@
import type { Config } from '../config/config.js';
import { AuthType } from '../core/contentGenerator.js';
import { createDebugLogger } from '../utils/debugLogger.js';
const debugLogger = createDebugLogger('FALLBACK');
export async function handleFallback(
config: Config,
@ -56,17 +59,17 @@ async function handleQwenOAuthError(error?: unknown): Promise<string | null> {
errorMessage.includes('too many requests');
if (isAuthError) {
console.warn('Qwen OAuth authentication error detected:', errorMessage);
debugLogger.warn('Qwen OAuth authentication error detected:', errorMessage);
// The QwenContentGenerator should automatically handle token refresh
// If it still fails, it likely means the refresh token is also expired
console.log(
debugLogger.info(
'Note: If this persists, you may need to re-authenticate with Qwen OAuth',
);
return null;
}
if (isRateLimitError) {
console.warn('Qwen API rate limit encountered:', errorMessage);
debugLogger.warn('Qwen API rate limit encountered:', errorMessage);
// For rate limiting, we don't need to do anything special
// The retry mechanism will handle the backoff
return null;

View file

@ -14,6 +14,9 @@ import {
} from './types.js';
import { DEFAULT_QWEN_MODEL } from '../config/models.js';
import { QWEN_OAUTH_MODELS } from './constants.js';
import { createDebugLogger } from '../utils/debugLogger.js';
const debugLogger = createDebugLogger('MODEL_REGISTRY');
export { QWEN_OAUTH_MODELS } from './constants.js';
@ -62,8 +65,8 @@ export class ModelRegistry {
const authType = validateAuthTypeKey(rawKey);
if (!authType) {
console.warn(
`[ModelRegistry] Invalid authType key "${rawKey}" in modelProviders config. Expected one of: ${Object.values(AuthType).join(', ')}. Skipping.`,
debugLogger.warn(
`Invalid authType key "${rawKey}" in modelProviders config. Expected one of: ${Object.values(AuthType).join(', ')}. Skipping.`,
);
continue;
}

View file

@ -5,6 +5,9 @@
*/
import type { DiscoveredMCPPrompt } from '../tools/mcp-client.js';
import { createDebugLogger } from '../utils/debugLogger.js';
const debugLogger = createDebugLogger('PROMPT_REGISTRY');
export class PromptRegistry {
private prompts: Map<string, DiscoveredMCPPrompt> = new Map();
@ -16,7 +19,7 @@ export class PromptRegistry {
registerPrompt(prompt: DiscoveredMCPPrompt): void {
if (this.prompts.has(prompt.name)) {
const newName = `${prompt.serverName}_${prompt.name}`;
console.warn(
debugLogger.warn(
`Prompt with name "${prompt.name}" is already registered. Renaming to "${newName}".`,
);
this.prompts.set(newName, { ...prompt, name: newName });

View file

@ -17,6 +17,9 @@ import {
} from '@google/genai';
import * as jsonl from '../utils/jsonl-utils.js';
import { getGitBranch } from '../utils/gitUtils.js';
import { createDebugLogger } from '../utils/debugLogger.js';
const debugLogger = createDebugLogger('CHAT_RECORDING');
import type {
ChatCompressionInfo,
ToolCallResponseInfo,
@ -247,7 +250,7 @@ export class ChatRecordingService {
jsonl.writeLineSync(conversationFile, record);
this.lastRecordUuid = record.uuid;
} catch (error) {
console.error('Error appending record:', error);
debugLogger.error('Error appending record:', error);
throw error;
}
}
@ -266,7 +269,7 @@ export class ChatRecordingService {
};
this.appendRecord(record);
} catch (error) {
console.error('Error saving user message:', error);
debugLogger.error('Error saving user message:', error);
}
}
@ -300,7 +303,7 @@ export class ChatRecordingService {
this.appendRecord(record);
} catch (error) {
console.error('Error saving assistant turn:', error);
debugLogger.error('Error saving assistant turn:', error);
}
}
@ -344,7 +347,7 @@ export class ChatRecordingService {
this.appendRecord(record);
} catch (error) {
console.error('Error saving tool result:', error);
debugLogger.error('Error saving tool result:', error);
}
}
@ -364,7 +367,7 @@ export class ChatRecordingService {
this.appendRecord(record);
} catch (error) {
console.error('Error saving slash command record:', error);
debugLogger.error('Error saving slash command record:', error);
}
}
@ -384,7 +387,7 @@ export class ChatRecordingService {
this.appendRecord(record);
} catch (error) {
console.error('Error saving chat compression record:', error);
debugLogger.error('Error saving chat compression record:', error);
}
}
@ -402,7 +405,7 @@ export class ChatRecordingService {
this.appendRecord(record);
} catch (error) {
console.error('Error saving ui telemetry record:', error);
debugLogger.error('Error saving ui telemetry record:', error);
}
}
}

View file

@ -23,6 +23,9 @@ import {
isFunctionResponse,
} from '../utils/messageInspectors.js';
import { DEFAULT_QWEN_MODEL } from '../config/models.js';
import { createDebugLogger } from '../utils/debugLogger.js';
const debugLogger = createDebugLogger('LOOP_DETECTION');
const TOOL_CALL_LOOP_THRESHOLD = 5;
const CONTENT_LOOP_THRESHOLD = 10;
@ -438,7 +441,7 @@ export class LoopDetectionService {
if (typeof result['confidence'] === 'number') {
if (result['confidence'] > 0.9) {
if (typeof result['reasoning'] === 'string' && result['reasoning']) {
console.warn(result['reasoning']);
debugLogger.warn(result['reasoning']);
}
logLoopDetected(
this.config,

View file

@ -17,6 +17,9 @@ import type {
UiTelemetryRecordPayload,
} from './chatRecordingService.js';
import { uiTelemetryService } from '../telemetry/uiTelemetry.js';
import { createDebugLogger } from '../utils/debugLogger.js';
const debugLogger = createDebugLogger('SESSION');
/**
* Session item for list display.
@ -324,7 +327,7 @@ export class SessionService {
return await jsonl.read<ChatRecord>(filePath);
} catch (error) {
if ((error as NodeJS.ErrnoException).code !== 'ENOENT') {
console.error('Error reading session file:', error);
debugLogger.error('Error reading session file:', error);
}
return [];
}

View file

@ -2,6 +2,9 @@ import type { SkillConfig, SkillValidationResult } from './types.js';
import * as fs from 'fs/promises';
import * as path from 'path';
import { parse as parseYaml } from '../utils/yaml-parser.js';
import { createDebugLogger } from '../utils/debugLogger.js';
const debugLogger = createDebugLogger('SKILL_LOAD');
const SKILL_MANIFEST_FILE = 'SKILL.md';
@ -26,7 +29,7 @@ export async function loadSkillsFromDir(
const config = parseSkillContent(content, skillManifest);
skills.push(config);
} catch (error) {
console.warn(
debugLogger.warn(
`Failed to parse skill at ${skillDir}: ${error instanceof Error ? error.message : 'Unknown error'}`,
);
continue;

View file

@ -19,6 +19,9 @@ import type {
import { SkillError, SkillErrorCode } from './types.js';
import type { Config } from '../config/config.js';
import { validateConfig } from './skill-load.js';
import { createDebugLogger } from '../utils/debugLogger.js';
const debugLogger = createDebugLogger('SKILL_MANAGER');
const QWEN_CONFIG_DIR = '.qwen';
const SKILLS_CONFIG_DIR = 'skills';
@ -57,7 +60,7 @@ export class SkillManager {
try {
listener();
} catch (error) {
console.warn('Skill change listener threw an error:', error);
debugLogger.warn('Skill change listener threw an error:', error);
}
}
}
@ -214,7 +217,7 @@ export class SkillManager {
stopWatching(): void {
for (const watcher of this.watchers.values()) {
void watcher.close().catch((error) => {
console.warn('Failed to close skills watcher:', error);
debugLogger.warn('Failed to close skills watcher:', error);
});
}
this.watchers.clear();
@ -426,7 +429,7 @@ export class SkillManager {
// Skip directories without valid SKILL.md
if (error instanceof SkillError) {
// Parse error was already recorded
console.warn(
debugLogger.warn(
`Failed to parse skill at ${skillDir}: ${error.message}`,
);
}
@ -486,7 +489,7 @@ export class SkillManager {
.get(existingPath)
?.close()
.catch((error) => {
console.warn(
debugLogger.warn(
`Failed to close skills watcher for ${existingPath}:`,
error,
);
@ -508,11 +511,11 @@ export class SkillManager {
this.scheduleRefresh();
})
.on('error', (error) => {
console.warn(`Skills watcher error for ${watchPath}:`, error);
debugLogger.warn(`Skills watcher error for ${watchPath}:`, error);
});
this.watchers.set(watchPath, watcher);
} catch (error) {
console.warn(
debugLogger.warn(
`Failed to watch skills directory at ${watchPath}:`,
error,
);
@ -536,7 +539,7 @@ export class SkillManager {
try {
await fs.mkdir(baseDir, { recursive: true });
} catch (error) {
console.warn(
debugLogger.warn(
`Failed to create user skills directory at ${baseDir}:`,
error,
);

View file

@ -28,6 +28,9 @@ import { SubagentError, SubagentErrorCode } from './types.js';
import { SubagentValidator } from './validation.js';
import { SubAgentScope } from './subagent.js';
import type { Config } from '../config/config.js';
import { createDebugLogger } from '../utils/debugLogger.js';
const debugLogger = createDebugLogger('SUBAGENT_MANAGER');
import { BuiltinAgentRegistry } from './builtin-agents.js';
import { ToolDisplayNamesMigration } from '../tools/tool-names.js';
@ -59,7 +62,7 @@ export class SubagentManager {
try {
listener();
} catch (error) {
console.warn('Subagent change listener threw an error:', error);
debugLogger.warn('Subagent change listener threw an error:', error);
}
}
}
@ -699,7 +702,7 @@ export class SubagentManager {
// If no match found, preserve the original identifier as-is
// This allows for tools that might not be registered yet or custom tools
result.push(toolIdentifier);
console.warn(
debugLogger.warn(
`Tool "${toolIdentifier}" not found in tool registry, preserving as-is`,
);
}

View file

@ -6,6 +6,9 @@
import { reportError } from '../utils/errorReporting.js';
import type { Config } from '../config/config.js';
import { createDebugLogger } from '../utils/debugLogger.js';
const debugLogger = createDebugLogger('SUBAGENT');
import { type ToolCallRequestInfo } from '../core/turn.js';
import {
CoreToolScheduler,
@ -515,7 +518,7 @@ export class SubAgentScope {
} as SubAgentRoundEvent);
}
} catch (error) {
console.error('Error during subagent execution:', error);
debugLogger.error('Error during subagent execution:', error);
this.terminateMode = SubagentTerminateMode.ERROR;
this.eventEmitter?.emit(SubAgentEventType.ERROR, {
subagentId: this.subagentId,

View file

@ -5,6 +5,9 @@
*/
import { execSync, spawn, spawnSync } from 'node:child_process';
import { createDebugLogger } from './debugLogger.js';
const debugLogger = createDebugLogger('EDITOR');
export type EditorType =
| 'vscode'
@ -174,7 +177,7 @@ export async function openDiff(
): Promise<void> {
const diffCommand = getDiffCommand(oldPath, newPath, editor);
if (!diffCommand) {
console.error('No diff tool available. Install a supported editor.');
debugLogger.error('No diff tool available. Install a supported editor.');
return;
}
@ -217,7 +220,7 @@ export async function openDiff(
});
});
} catch (error) {
console.error(error);
debugLogger.error(error);
throw error;
}
}

View file

@ -7,6 +7,9 @@
import type { Content, Part } from '@google/genai';
import type { Config } from '../config/config.js';
import { getFolderStructure } from './getFolderStructure.js';
import { createDebugLogger } from './debugLogger.js';
const debugLogger = createDebugLogger('ENV_CONTEXT');
/**
* Generates a string describing the current workspace directories and their structures.
@ -87,18 +90,18 @@ ${directoryContext}
text: `\n--- Full File Context ---\n${result.llmContent}`,
});
} else {
console.warn(
debugLogger.warn(
'Full context requested, but read_many_files returned no content.',
);
}
} else {
console.warn(
debugLogger.warn(
'Full context requested, but read_many_files tool not found.',
);
}
} catch (error) {
// Not using reportError here as it's a startup/config phase, not a chat/generation phase error.
console.error('Error reading full file context:', error);
debugLogger.error('Error reading full file context:', error);
initialParts.push({
text: '\n--- Error reading full file context ---',
});

View file

@ -12,6 +12,9 @@ import mime from 'mime/lite';
import { ToolErrorType } from '../tools/tool-error.js';
import { BINARY_EXTENSIONS } from './ignorePatterns.js';
import type { Config } from '../config/config.js';
import { createDebugLogger } from './debugLogger.js';
const debugLogger = createDebugLogger('FILE_UTILS');
// Default values for encoding and separator format
export const DEFAULT_ENCODING: BufferEncoding = 'utf-8';
@ -218,7 +221,7 @@ export async function isBinaryFile(filePath: string): Promise<boolean> {
// If >30% non-printable characters, consider it binary
return nonPrintableCount / bytesRead > 0.3;
} catch (error) {
console.warn(
debugLogger.warn(
`Failed to check if file is binary: ${filePath}`,
error instanceof Error ? error.message : String(error),
);
@ -228,7 +231,7 @@ export async function isBinaryFile(filePath: string): Promise<boolean> {
try {
await fh.close();
} catch (closeError) {
console.warn(
debugLogger.warn(
`Failed to close file handle for: ${filePath}`,
closeError instanceof Error ? closeError.message : String(closeError),
);

View file

@ -11,6 +11,9 @@ import { getErrorMessage, isNodeError } from './errors.js';
import type { FileDiscoveryService } from '../services/fileDiscoveryService.js';
import type { FileFilteringOptions } from '../config/constants.js';
import { DEFAULT_FILE_FILTERING_OPTIONS } from '../config/constants.js';
import { createDebugLogger } from './debugLogger.js';
const debugLogger = createDebugLogger('FOLDER_STRUCTURE');
const MAX_ITEMS = 20;
const TRUNCATION_INDICATOR = '...';
@ -104,7 +107,7 @@ async function readFullStructure(
isNodeError(error) &&
(error.code === 'EACCES' || error.code === 'ENOENT')
) {
console.warn(
debugLogger.warn(
`Warning: Could not read directory ${currentPath}: ${error.message}`,
);
if (currentPath === rootPath && error.code === 'ENOENT') {
@ -342,7 +345,10 @@ export async function getFolderStructure(
return `${summary}\n\n${resolvedPath}${path.sep}\n${structureLines.join('\n')}`;
} catch (error: unknown) {
console.error(`Error getting folder structure for ${resolvedPath}:`, error);
debugLogger.error(
`Error getting folder structure for ${resolvedPath}:`,
error,
);
return `Error processing directory "${resolvedPath}": ${getErrorMessage(error)}`;
}
}

View file

@ -8,6 +8,9 @@ import * as fs from 'node:fs';
import { randomUUID } from 'node:crypto';
import * as path from 'node:path';
import { Storage } from '../config/storage.js';
import { createDebugLogger } from '../utils/debugLogger.js';
const debugLogger = createDebugLogger('INSTALLATION');
export class InstallationManager {
private getInstallationIdPath(): string {
@ -48,7 +51,7 @@ export class InstallationManager {
return installationId;
} catch (error) {
console.error(
debugLogger.error(
'Error accessing installation ID file, generating ephemeral ID:',
error,
);

View file

@ -25,6 +25,9 @@ import fs from 'node:fs';
import path from 'node:path';
import readline from 'node:readline';
import { Mutex } from 'async-mutex';
import { createDebugLogger } from './debugLogger.js';
const debugLogger = createDebugLogger('JSONL');
/**
* A map of file paths to mutexes for preventing concurrent writes.
@ -68,7 +71,7 @@ export async function readLines<T = unknown>(
return results;
} catch (error) {
if ((error as NodeJS.ErrnoException).code !== 'ENOENT') {
console.error(
debugLogger.error(
`Error reading first ${count} lines from ${filePath}:`,
error,
);
@ -100,7 +103,7 @@ export async function read<T = unknown>(filePath: string): Promise<T[]> {
return results;
} catch (error) {
if ((error as NodeJS.ErrnoException).code !== 'ENOENT') {
console.error(`Error reading ${filePath}:`, error);
debugLogger.error(`Error reading ${filePath}:`, error);
}
return [];
}
@ -174,7 +177,7 @@ export async function countLines(filePath: string): Promise<number> {
return count;
} catch (error) {
if ((error as NodeJS.ErrnoException).code !== 'ENOENT') {
console.error(`Error counting lines in ${filePath}:`, error);
debugLogger.error(`Error counting lines in ${filePath}:`, error);
}
return 0;
}

View file

@ -10,6 +10,9 @@ import { type BaseLlmClient } from '../core/baseLlmClient.js';
import { LruCache } from './LruCache.js';
import { DEFAULT_QWEN_FLASH_MODEL } from '../config/models.js';
import { promptIdContext } from './promptIdContext.js';
import { createDebugLogger } from './debugLogger.js';
const debugLogger = createDebugLogger('LLM_EDIT_FIXER');
const MAX_CACHE_SIZE = 50;
@ -112,7 +115,7 @@ export async function FixLLMEditWithInstruction(
let promptId = promptIdContext.getStore();
if (!promptId) {
promptId = `llm-fixer-fallback-${Date.now()}-${Math.random().toString(16).slice(2)}`;
console.warn(
debugLogger.warn(
`Could not find promptId in context. This is unexpected. Using a fallback ID: ${promptId}`,
);
}

View file

@ -9,6 +9,9 @@ import { DEFAULT_QWEN_MODEL } from '../config/models.js';
import type { GeminiChat } from '../core/geminiChat.js';
import { isFunctionResponse } from './messageInspectors.js';
import type { Config } from '../config/config.js';
import { createDebugLogger } from './debugLogger.js';
const debugLogger = createDebugLogger('NEXT_SPEAKER');
const CHECK_PROMPT = `Analyze *only* the content and structure of your immediately preceding response (your last turn in the conversation history). Based *strictly* on that response, determine who should logically speak next: the 'user' or the 'model' (you).
**Decision Rules (apply in order):**
@ -126,7 +129,7 @@ export async function checkNextSpeaker(
}
return null;
} catch (error) {
console.warn(
debugLogger.warn(
'Failed to talk to Gemini endpoint when seeing if conversation should continue.',
error,
);

View file

@ -8,6 +8,9 @@ import * as path from 'node:path';
import { promises as fs } from 'node:fs';
import { v4 as uuidv4 } from 'uuid';
import * as os from 'os';
import { createDebugLogger } from './debugLogger.js';
const debugLogger = createDebugLogger('OPENAI_LOGGER');
/**
* Logger specifically for OpenAI API requests and responses
@ -47,7 +50,7 @@ export class OpenAILogger {
await fs.mkdir(this.logDir, { recursive: true });
this.initialized = true;
} catch (error) {
console.error('Failed to initialize OpenAI logger:', error);
debugLogger.error('Failed to initialize OpenAI logger:', error);
throw new Error(`Failed to initialize OpenAI logger: ${error}`);
}
}
@ -95,7 +98,7 @@ export class OpenAILogger {
await fs.writeFile(filePath, JSON.stringify(logData, null, 2), 'utf-8');
return filePath;
} catch (writeError) {
console.error('Failed to write OpenAI log file:', writeError);
debugLogger.error('Failed to write OpenAI log file:', writeError);
throw new Error(`Failed to write OpenAI log file: ${writeError}`);
}
}
@ -123,7 +126,7 @@ export class OpenAILogger {
if ((error as NodeJS.ErrnoException).code === 'ENOENT') {
return [];
}
console.error('Failed to read OpenAI log directory:', error);
debugLogger.error('Failed to read OpenAI log directory:', error);
return [];
}
}
@ -138,7 +141,7 @@ export class OpenAILogger {
const content = await fs.readFile(filePath, 'utf-8');
return JSON.parse(content);
} catch (error) {
console.error(`Failed to read log file ${filePath}:`, error);
debugLogger.error(`Failed to read log file ${filePath}:`, error);
throw new Error(`Failed to read log file: ${error}`);
}
}

View file

@ -6,6 +6,9 @@
import type { ImageMetadata } from './types.js';
import { isSupportedImageMimeType } from './supportedImageFormats.js';
import { createDebugLogger } from '../debugLogger.js';
const debugLogger = createDebugLogger('IMAGE_TOKENIZER');
/**
* Image tokenizer for calculating image tokens based on dimensions
@ -44,7 +47,7 @@ export class ImageTokenizer {
try {
// Check if the MIME type is supported
if (!isSupportedImageMimeType(mimeType)) {
console.warn(`Unsupported image format: ${mimeType}`);
debugLogger.warn(`Unsupported image format: ${mimeType}`);
// Return default metadata for unsupported formats
return {
width: 512,
@ -65,7 +68,7 @@ export class ImageTokenizer {
dataSize: buffer.length,
};
} catch (error) {
console.warn('Failed to extract image metadata:', error);
debugLogger.warn('Failed to extract image metadata:', error);
// Return default metadata for fallback
return {
width: 512,
@ -320,7 +323,7 @@ export class ImageTokenizer {
const metadata = await this.extractImageMetadata(data, mimeType);
results.push(this.calculateTokens(metadata));
} catch (error) {
console.warn('Error calculating tokens for image:', error);
debugLogger.warn('Error calculating tokens for image:', error);
// Return minimum tokens as fallback
results.push(
ImageTokenizer.MIN_TOKENS_PER_IMAGE +
@ -499,7 +502,7 @@ export class ImageTokenizer {
}
// Fallback: return default dimensions if we can't parse the structure
console.warn('Could not extract HEIC dimensions, using default');
debugLogger.warn('Could not extract HEIC dimensions, using default');
return { width: 512, height: 512 };
}
}

View file

@ -13,6 +13,9 @@ import type {
import type { TokenCalculationResult } from './types.js';
import { TextTokenizer } from './textTokenizer.js';
import { ImageTokenizer } from './imageTokenizer.js';
import { createDebugLogger } from '../debugLogger.js';
const debugLogger = createDebugLogger('TOKENIZER');
/**
* Simple request token estimator that handles text and image content serially
@ -77,7 +80,7 @@ export class RequestTokenizer {
processingTime,
};
} catch (error) {
console.error('Error calculating tokens:', error);
debugLogger.error('Error calculating tokens:', error);
// Fallback calculation
const fallbackTokens = this.calculateFallbackTokens(request);
@ -105,7 +108,7 @@ export class RequestTokenizer {
// Avoid per-part rounding inflation by estimating once on the combined text.
return await this.textTokenizer.calculateTokens(textContents.join(''));
} catch (error) {
console.warn('Error calculating text tokens:', error);
debugLogger.warn('Error calculating text tokens:', error);
// Fallback: character-based estimation
const totalChars = textContents.join('').length;
return Math.ceil(totalChars / 4);
@ -125,7 +128,7 @@ export class RequestTokenizer {
await this.imageTokenizer.calculateTokensBatch(imageContents);
return tokenCounts.reduce((sum, count) => sum + count, 0);
} catch (error) {
console.warn('Error calculating image tokens:', error);
debugLogger.warn('Error calculating image tokens:', error);
// Fallback: minimum tokens per image
return imageContents.length * 6; // 4 image tokens + 2 special tokens as minimum
}
@ -151,7 +154,7 @@ export class RequestTokenizer {
// Rough estimate: 1 token per 100 bytes of audio data
totalTokens += Math.max(Math.ceil(dataSize / 100), 10); // Minimum 10 tokens per audio
} catch (error) {
console.warn('Error calculating audio tokens:', error);
debugLogger.warn('Error calculating audio tokens:', error);
totalTokens += 10; // Fallback minimum
}
}
@ -169,7 +172,7 @@ export class RequestTokenizer {
// Treat other content as text, and avoid per-item rounding inflation.
return await this.textTokenizer.calculateTokens(otherContents.join(''));
} catch (error) {
console.warn('Error calculating other content tokens:', error);
debugLogger.warn('Error calculating other content tokens:', error);
// Fallback: character-based estimation
const totalChars = otherContents.join('').length;
return Math.ceil(totalChars / 4);
@ -184,7 +187,7 @@ export class RequestTokenizer {
const content = JSON.stringify(request.contents);
return Math.ceil(content.length / 4); // Rough estimate: 1 token ≈ 4 characters
} catch (error) {
console.warn('Error in fallback token calculation:', error);
debugLogger.warn('Error in fallback token calculation:', error);
return 100; // Conservative fallback
}
}
@ -321,7 +324,7 @@ export class RequestTokenizer {
otherContents.push(serialized);
}
} catch (error) {
console.warn('Failed to serialize unknown part type:', error);
debugLogger.warn('Failed to serialize unknown part type:', error);
}
}
}

View file

@ -156,7 +156,7 @@ export async function retryWithBackoff<T>(
if (delayDurationMs > 0) {
// Respect Retry-After header if present and parsed
console.warn(
debugLogger.warn(
`Attempt ${attempt} failed with status ${delayErrorStatus ?? 'unknown'}. Retrying after explicit delay of ${delayDurationMs}ms...`,
error,
);
@ -279,25 +279,25 @@ function logRetryAttempt(
}
if (errorStatus === 429) {
console.warn(message, error);
debugLogger.warn(message, error);
} else if (errorStatus && errorStatus >= 500 && errorStatus < 600) {
console.error(message, error);
debugLogger.error(message, error);
} else if (error instanceof Error) {
// Fallback for errors that might not have a status but have a message
if (error.message.includes('429')) {
console.warn(
debugLogger.warn(
`Attempt ${attempt} failed with 429 error (no Retry-After header). Retrying with backoff...`,
error,
);
} else if (error.message.match(/5\d{2}/)) {
console.error(
debugLogger.error(
`Attempt ${attempt} failed with 5xx error. Retrying with backoff...`,
error,
);
} else {
console.warn(message, error); // Default to warn for other errors
debugLogger.warn(message, error); // Default to warn for other errors
}
} else {
console.warn(message, error); // Default to warn if error type is unknown
debugLogger.warn(message, error); // Default to warn if error type is unknown
}
}

View file

@ -9,6 +9,9 @@ import { fileURLToPath } from 'node:url';
import { execFile } from 'node:child_process';
import { fileExists } from './fileUtils.js';
import { execCommand, isCommandAvailable } from './shell-utils.js';
import { createDebugLogger } from './debugLogger.js';
const debugLogger = createDebugLogger('RIPGREP');
const RIPGREP_COMMAND = 'rg';
const RIPGREP_BUFFER_LIMIT = 20_000_000; // Keep buffers aligned with the original bundle.
@ -313,7 +316,7 @@ export async function runRipgrep(
// Log warnings for abnormal exits (except syntax errors)
if (!syntaxError && truncated) {
console.warn(
debugLogger.warn(
`ripgrep exited abnormally (signal=${error.signal} code=${error.code}) with stderr:\n${stderr.trim() || '(empty)'}`,
);
}

View file

@ -5,6 +5,9 @@
*/
import { jsonrepair } from 'jsonrepair';
import { createDebugLogger } from './debugLogger.js';
const debugLogger = createDebugLogger('JSON_PARSE');
/**
* Safely parse JSON string with jsonrepair fallback for malformed JSON.
@ -34,7 +37,7 @@ export function safeJsonParse<T = Record<string, unknown>>(
// jsonrepair always returns a string, so we need to parse it
return JSON.parse(repairedJson) as T;
} catch (repairError) {
console.error('Failed to parse JSON even with jsonrepair:', {
debugLogger.error('Failed to parse JSON even with jsonrepair:', {
originalError: error,
repairError,
jsonString,

View file

@ -13,6 +13,9 @@ import type {
import type { GeminiClient } from '../core/client.js';
import { DEFAULT_QWEN_FLASH_MODEL } from '../config/models.js';
import { getResponseText, partToString } from './partUtils.js';
import { createDebugLogger } from './debugLogger.js';
const debugLogger = createDebugLogger('SUMMARIZER');
/**
* A function that summarizes the result of a tool execution.
@ -90,7 +93,7 @@ export async function summarizeToolOutput(
)) as unknown as GenerateContentResponse;
return getResponseText(parsedResponse) || textToSummarize;
} catch (error) {
console.error('Failed to summarize tool output.', error);
debugLogger.error('Failed to summarize tool output.', error);
return textToSummarize;
}
}

View file

@ -7,6 +7,9 @@
import { execSync } from 'node:child_process';
import os from 'node:os';
import { detect as chardetDetect } from 'chardet';
import { createDebugLogger } from './debugLogger.js';
const debugLogger = createDebugLogger('ENCODING');
// Cache for system encoding to avoid repeated detection
// Use undefined to indicate "not yet checked" vs null meaning "checked but failed"
@ -75,7 +78,7 @@ export function getSystemEncoding(): string | null {
`Unable to parse Windows code page from 'chcp' output "${output.trim()}". `,
);
} catch (error) {
console.warn(
debugLogger.warn(
`Failed to get Windows code page using 'chcp' command: ${error instanceof Error ? error.message : String(error)}. ` +
`Will attempt to detect encoding from command output instead.`,
);
@ -97,7 +100,7 @@ export function getSystemEncoding(): string | null {
.toString()
.trim();
} catch (_e) {
console.warn('Failed to get locale charmap.');
debugLogger.warn('Failed to get locale charmap.');
return null;
}
}
@ -150,7 +153,7 @@ export function windowsCodePageToEncoding(cp: number): string | null {
return map[cp];
}
console.warn(`Unable to determine encoding for windows code page ${cp}.`);
debugLogger.warn(`Unable to determine encoding for windows code page ${cp}.`);
return null; // Return null if no mapping found
}
@ -168,7 +171,7 @@ export function detectEncodingFromBuffer(buffer: Buffer): string | null {
return detected.toLowerCase();
}
} catch (error) {
console.warn('Failed to detect encoding with chardet:', error);
debugLogger.warn('Failed to detect encoding with chardet:', error);
}
return null;

View file

@ -8,6 +8,9 @@ import { isNodeError } from '../utils/errors.js';
import * as fs from 'node:fs';
import * as path from 'node:path';
import * as process from 'node:process';
import { createDebugLogger } from './debugLogger.js';
const debugLogger = createDebugLogger('WORKSPACE');
export type Unsubscribe = () => void;
@ -53,7 +56,7 @@ export class WorkspaceContext {
listener();
} catch (e) {
// Don't let one listener break others.
console.error('Error in WorkspaceContext listener:', e);
debugLogger.error('Error in WorkspaceContext listener:', e);
}
}
}
@ -72,8 +75,8 @@ export class WorkspaceContext {
this.directories.add(resolved);
this.notifyDirectoriesChanged();
} catch (err) {
console.warn(
`[WARN] Skipping unreadable directory: ${directory} (${err instanceof Error ? err.message : String(err)})`,
debugLogger.warn(
`Skipping unreadable directory: ${directory} (${err instanceof Error ? err.message : String(err)})`,
);
}
}