centaurus-cli 3.1.1 → 3.1.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -6,6 +6,7 @@ let fetchPromise = null;
6
6
  const FALLBACK_CONFIG = {
7
7
  models: [
8
8
  {
9
+ uid: "gemini-2-5-flash",
9
10
  id: "gemini-2.5-flash",
10
11
  name: "Gemini 2.5 Flash",
11
12
  description: "Fast and efficient",
@@ -67,6 +68,7 @@ function getModelContextWindowSync(modelNameOrId) {
67
68
  const nameLower = modelNameOrId.toLowerCase();
68
69
  if (nameLower.includes("kimi")) return 128e3;
69
70
  if (nameLower.includes("glm")) return 128e3;
71
+ if (nameLower.includes("minimax")) return 128e3;
70
72
  if (nameLower.includes("gemini")) return 1e6;
71
73
  return 1e6;
72
74
  }
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/config/models.ts"],"sourcesContent":["/**\r\n * Supported AI models\r\n * Fetched from backend API with caching\r\n */\r\n\r\nimport { apiClient, ModelConfig, ModelsConfig } from '../services/api-client.js';\r\nimport { logWarning } from '../utils/logger.js';\r\nimport { quickLog } from '../utils/conversation-logger.js';\r\n\r\n// Re-export types\r\nexport type { ModelConfig, ModelsConfig };\r\n\r\n/**\r\n * Cached models configuration\r\n * Fetched once per session from backend\r\n */\r\nlet cachedConfig: ModelsConfig | null = null;\r\nlet fetchPromise: Promise<ModelsConfig> | null = null;\r\n\r\n/**\r\n * Default fallback configuration when backend is unavailable\r\n */\r\nconst FALLBACK_CONFIG: ModelsConfig = {\r\n models: [\r\n {\r\n id: 'gemini-2.5-flash',\r\n name: 'Gemini 2.5 Flash',\r\n description: 'Fast and efficient',\r\n provider: 'google',\r\n contextWindow: 1000000,\r\n region: 'us-central1',\r\n supportsThinking: true,\r\n thinkingConfig: { thinking_config: { include_thoughts: true } },\r\n generationConfig: {\r\n temperature: 0.1,\r\n topP: 0.95,\r\n maxOutputTokens: 16384\r\n }\r\n }\r\n ],\r\n defaultModel: 'gemini-2.5-flash'\r\n};\r\n\r\n/**\r\n * Fetch models configuration from backend (with caching)\r\n * This is the main entry point for getting model config\r\n */\r\nexport async function fetchModelsConfig(): Promise<ModelsConfig> {\r\n // Return cached config if available\r\n if (cachedConfig) {\r\n return cachedConfig;\r\n }\r\n\r\n // If a fetch is already in progress, wait for it\r\n if (fetchPromise) {\r\n return fetchPromise;\r\n }\r\n\r\n // Start fetching from backend\r\n fetchPromise = (async () => {\r\n try {\r\n const config = await apiClient.getModelsConfig();\r\n cachedConfig = config;\r\n return config;\r\n } catch (error) {\r\n // On error, use fallback config\r\n logWarning('Failed to fetch models config from backend, using fallback');\r\n cachedConfig = FALLBACK_CONFIG;\r\n return FALLBACK_CONFIG;\r\n } finally {\r\n fetchPromise = null;\r\n }\r\n })();\r\n\r\n return fetchPromise;\r\n}\r\n\r\n/**\r\n * Clear cached config (useful for refreshing)\r\n */\r\nexport function clearModelsCache(): void {\r\n cachedConfig = null;\r\n fetchPromise = null;\r\n}\r\n\r\n/**\r\n * Get context window size for a model SYNCHRONOUSLY from cache\r\n * This is useful for UI components that need immediate values\r\n * Returns fallback defaults if cache is not yet populated\r\n * @param modelNameOrId - Model name or ID (case-insensitive search)\r\n * @returns Context window size in tokens\r\n */\r\nexport function getModelContextWindowSync(modelNameOrId: string): number {\r\n // DEBUG: Log cache state to file\r\n quickLog(`[${new Date().toISOString()}] [getModelContextWindowSync] Looking up: \"${modelNameOrId}\", cache populated: ${!!cachedConfig}, models in cache: ${cachedConfig?.models?.length || 0}\\n`);\r\n\r\n // Try to find in cache first\r\n if (cachedConfig) {\r\n const model = cachedConfig.models.find(m =>\r\n m.name.toLowerCase() === modelNameOrId.toLowerCase() ||\r\n m.id.toLowerCase() === modelNameOrId.toLowerCase()\r\n );\r\n if (model) {\r\n quickLog(`[${new Date().toISOString()}] [getModelContextWindowSync] Found model \"${model.name}\" with contextWindow: ${model.contextWindow}\\n`);\r\n return model.contextWindow;\r\n }\r\n quickLog(`[${new Date().toISOString()}] [getModelContextWindowSync] Model NOT found in cache. Available: ${cachedConfig.models.map(m => m.name).join(', ')}\\n`);\r\n }\r\n\r\n // Fallback defaults based on known patterns\r\n const nameLower = modelNameOrId.toLowerCase();\r\n if (nameLower.includes('kimi')) return 128000;\r\n if (nameLower.includes('glm')) return 128000;\r\n if (nameLower.includes('gemini')) return 1000000;\r\n return 1000000; // Default 1M tokens\r\n}\r\n\r\n\r\n/**\r\n * Get all model configurations (async)\r\n * @returns Promise resolving to array of all model configurations\r\n */\r\nexport async function getAllModelConfigs(): Promise<ModelConfig[]> {\r\n const config = await fetchModelsConfig();\r\n return config.models;\r\n}\r\n\r\n/**\r\n * Get model configuration by ID (async)\r\n * @param modelId - Model ID\r\n * @returns Promise resolving to model configuration or undefined\r\n */\r\nexport async function getModelConfig(modelId: string): Promise<ModelConfig | undefined> {\r\n const config = await fetchModelsConfig();\r\n return config.models.find(m => m.id === modelId);\r\n}\r\n\r\n/**\r\n * Get model configuration by ID and name (for duplicate model IDs with different configs)\r\n * @param modelId - Model ID\r\n * @param modelName - Model display name\r\n * @returns Promise resolving to model configuration or undefined\r\n */\r\nexport async function getModelConfigByIdAndName(modelId: string, modelName: string): Promise<ModelConfig | undefined> {\r\n const config = await fetchModelsConfig();\r\n return config.models.find(m => m.id === modelId && m.name === modelName);\r\n}\r\n\r\n/**\r\n * Get default model ID (async)\r\n * @returns Promise resolving to default model ID\r\n */\r\nexport async function getDefaultModel(): Promise<string> {\r\n const config = await fetchModelsConfig();\r\n return config.defaultModel;\r\n}\r\n\r\n/**\r\n * Get list of supported model IDs (unique) (async)\r\n * Excludes internal models that are not meant to be user-selectable\r\n * @returns Promise resolving to array of unique model IDs\r\n */\r\nexport async function getSupportedModels(): Promise<string[]> {\r\n const config = await fetchModelsConfig();\r\n // Backend already filters allowFrontendDisplay: false, but we check here too for robustness\r\n const userFacingModels = config.models.filter(m => m.allowFrontendDisplay !== false);\r\n return Array.from(new Set(userFacingModels.map(m => m.id)));\r\n}\r\n\r\n/**\r\n * Validate if a model name is supported (async)\r\n * @param model - Model name to validate\r\n * @returns Promise resolving to true if model is supported\r\n */\r\nexport async function isValidModel(model: string): Promise<boolean> {\r\n const models = await getSupportedModels();\r\n return models.includes(model);\r\n}\r\n\r\n/**\r\n * Get a user-friendly error message for invalid models (async)\r\n * @param invalidModel - The invalid model name\r\n * @returns Promise resolving to error message with list of valid models\r\n */\r\nexport async function getInvalidModelError(invalidModel: string): Promise<string> {\r\n const config = await fetchModelsConfig();\r\n // Only show user-facing models\r\n const userFacingModels = config.models.filter(m => m.allowFrontendDisplay !== false);\r\n return `Invalid model: ${invalidModel}\\n\\n` +\r\n `Supported models:\\n` +\r\n userFacingModels.map(m => ` - ${m.id} (${m.name})`).join('\\n') +\r\n `\\n\\nUse /model to select from available models.`;\r\n}\r\n\r\n/**\r\n * Get model display name with description (async)\r\n * @param model - Model ID\r\n * @returns Promise resolving to display name with description\r\n */\r\nexport async function getModelDisplayName(model: string): Promise<string> {\r\n const config = await getModelConfig(model);\r\n if (config) {\r\n return `${config.name} - ${config.description}`;\r\n }\r\n return model;\r\n}\r\n\r\n/**\r\n * Get context window size for a model (async)\r\n * @param model - Model ID\r\n * @returns Promise resolving to context window size in tokens\r\n */\r\nexport async function getModelContextWindow(model: string): Promise<number> {\r\n const config = await getModelConfig(model);\r\n return config?.contextWindow || 2000000; // Default 2M tokens\r\n}\r\n\r\n/**\r\n * Check if model supports thinking (async)\r\n * @param model - Model ID\r\n * @returns Promise resolving to true if model supports thinking\r\n */\r\nexport async function modelSupportsThinking(model: string): Promise<boolean> {\r\n const config = await getModelConfig(model);\r\n return config?.supportsThinking || false;\r\n}\r\n\r\n\r\n"],"mappings":"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","names":[]}
1
+ {"version":3,"sources":["../../src/config/models.ts"],"sourcesContent":["/**\r\n * Supported AI models\r\n * Fetched from backend API with caching\r\n */\r\n\r\nimport { apiClient, ModelConfig, ModelsConfig } from '../services/api-client.js';\r\nimport { logWarning } from '../utils/logger.js';\r\nimport { quickLog } from '../utils/conversation-logger.js';\r\n\r\n// Re-export types\r\nexport type { ModelConfig, ModelsConfig };\r\n\r\n/**\r\n * Cached models configuration\r\n * Fetched once per session from backend\r\n */\r\nlet cachedConfig: ModelsConfig | null = null;\r\nlet fetchPromise: Promise<ModelsConfig> | null = null;\r\n\r\n/**\r\n * Default fallback configuration when backend is unavailable\r\n */\r\nconst FALLBACK_CONFIG: ModelsConfig = {\r\n models: [\r\n {\r\n uid: 'gemini-2-5-flash',\r\n id: 'gemini-2.5-flash',\r\n name: 'Gemini 2.5 Flash',\r\n description: 'Fast and efficient',\r\n provider: 'google',\r\n contextWindow: 1000000,\r\n region: 'us-central1',\r\n supportsThinking: true,\r\n thinkingConfig: { thinking_config: { include_thoughts: true } },\r\n generationConfig: {\r\n temperature: 0.1,\r\n topP: 0.95,\r\n maxOutputTokens: 16384\r\n }\r\n }\r\n ],\r\n defaultModel: 'gemini-2.5-flash'\r\n};\r\n\r\n/**\r\n * Fetch models configuration from backend (with caching)\r\n * This is the main entry point for getting model config\r\n */\r\nexport async function fetchModelsConfig(): Promise<ModelsConfig> {\r\n // Return cached config if available\r\n if (cachedConfig) {\r\n return cachedConfig;\r\n }\r\n\r\n // If a fetch is already in progress, wait for it\r\n if (fetchPromise) {\r\n return fetchPromise;\r\n }\r\n\r\n // Start fetching from backend\r\n fetchPromise = (async () => {\r\n try {\r\n const config = await apiClient.getModelsConfig();\r\n cachedConfig = config;\r\n return config;\r\n } catch (error) {\r\n // On error, use fallback config\r\n logWarning('Failed to fetch models config from backend, using fallback');\r\n cachedConfig = FALLBACK_CONFIG;\r\n return FALLBACK_CONFIG;\r\n } finally {\r\n fetchPromise = null;\r\n }\r\n })();\r\n\r\n return fetchPromise;\r\n}\r\n\r\n/**\r\n * Clear cached config (useful for refreshing)\r\n */\r\nexport function clearModelsCache(): void {\r\n cachedConfig = null;\r\n fetchPromise = null;\r\n}\r\n\r\n/**\r\n * Get context window size for a model SYNCHRONOUSLY from cache\r\n * This is useful for UI components that need immediate values\r\n * Returns fallback defaults if cache is not yet populated\r\n * @param modelNameOrId - Model name or ID (case-insensitive search)\r\n * @returns Context window size in tokens\r\n */\r\nexport function getModelContextWindowSync(modelNameOrId: string): number {\r\n // DEBUG: Log cache state to file\r\n quickLog(`[${new Date().toISOString()}] [getModelContextWindowSync] Looking up: \"${modelNameOrId}\", cache populated: ${!!cachedConfig}, models in cache: ${cachedConfig?.models?.length || 0}\\n`);\r\n\r\n // Try to find in cache first\r\n if (cachedConfig) {\r\n const model = cachedConfig.models.find(m =>\r\n m.name.toLowerCase() === modelNameOrId.toLowerCase() ||\r\n m.id.toLowerCase() === modelNameOrId.toLowerCase()\r\n );\r\n if (model) {\r\n quickLog(`[${new Date().toISOString()}] [getModelContextWindowSync] Found model \"${model.name}\" with contextWindow: ${model.contextWindow}\\n`);\r\n return model.contextWindow;\r\n }\r\n quickLog(`[${new Date().toISOString()}] [getModelContextWindowSync] Model NOT found in cache. Available: ${cachedConfig.models.map(m => m.name).join(', ')}\\n`);\r\n }\r\n\r\n // Fallback defaults based on known patterns\r\n const nameLower = modelNameOrId.toLowerCase();\r\n if (nameLower.includes('kimi')) return 128000;\r\n if (nameLower.includes('glm')) return 128000;\r\n if (nameLower.includes('minimax')) return 128000;\r\n if (nameLower.includes('gemini')) return 1000000;\r\n return 1000000; // Default 1M tokens\r\n}\r\n\r\n\r\n/**\r\n * Get all model configurations (async)\r\n * @returns Promise resolving to array of all model configurations\r\n */\r\nexport async function getAllModelConfigs(): Promise<ModelConfig[]> {\r\n const config = await fetchModelsConfig();\r\n return config.models;\r\n}\r\n\r\n/**\r\n * Get model configuration by ID (async)\r\n * @param modelId - Model ID\r\n * @returns Promise resolving to model configuration or undefined\r\n */\r\nexport async function getModelConfig(modelId: string): Promise<ModelConfig | undefined> {\r\n const config = await fetchModelsConfig();\r\n return config.models.find(m => m.id === modelId);\r\n}\r\n\r\n/**\r\n * Get model configuration by ID and name (for duplicate model IDs with different configs)\r\n * @param modelId - Model ID\r\n * @param modelName - Model display name\r\n * @returns Promise resolving to model configuration or undefined\r\n */\r\nexport async function getModelConfigByIdAndName(modelId: string, modelName: string): Promise<ModelConfig | undefined> {\r\n const config = await fetchModelsConfig();\r\n return config.models.find(m => m.id === modelId && m.name === modelName);\r\n}\r\n\r\n/**\r\n * Get default model ID (async)\r\n * @returns Promise resolving to default model ID\r\n */\r\nexport async function getDefaultModel(): Promise<string> {\r\n const config = await fetchModelsConfig();\r\n return config.defaultModel;\r\n}\r\n\r\n/**\r\n * Get list of supported model IDs (unique) (async)\r\n * Excludes internal models that are not meant to be user-selectable\r\n * @returns Promise resolving to array of unique model IDs\r\n */\r\nexport async function getSupportedModels(): Promise<string[]> {\r\n const config = await fetchModelsConfig();\r\n // Backend already filters allowFrontendDisplay: false, but we check here too for robustness\r\n const userFacingModels = config.models.filter(m => m.allowFrontendDisplay !== false);\r\n return Array.from(new Set(userFacingModels.map(m => m.id)));\r\n}\r\n\r\n/**\r\n * Validate if a model name is supported (async)\r\n * @param model - Model name to validate\r\n * @returns Promise resolving to true if model is supported\r\n */\r\nexport async function isValidModel(model: string): Promise<boolean> {\r\n const models = await getSupportedModels();\r\n return models.includes(model);\r\n}\r\n\r\n/**\r\n * Get a user-friendly error message for invalid models (async)\r\n * @param invalidModel - The invalid model name\r\n * @returns Promise resolving to error message with list of valid models\r\n */\r\nexport async function getInvalidModelError(invalidModel: string): Promise<string> {\r\n const config = await fetchModelsConfig();\r\n // Only show user-facing models\r\n const userFacingModels = config.models.filter(m => m.allowFrontendDisplay !== false);\r\n return `Invalid model: ${invalidModel}\\n\\n` +\r\n `Supported models:\\n` +\r\n userFacingModels.map(m => ` - ${m.id} (${m.name})`).join('\\n') +\r\n `\\n\\nUse /model to select from available models.`;\r\n}\r\n\r\n/**\r\n * Get model display name with description (async)\r\n * @param model - Model ID\r\n * @returns Promise resolving to display name with description\r\n */\r\nexport async function getModelDisplayName(model: string): Promise<string> {\r\n const config = await getModelConfig(model);\r\n if (config) {\r\n return `${config.name} - ${config.description}`;\r\n }\r\n return model;\r\n}\r\n\r\n/**\r\n * Get context window size for a model (async)\r\n * @param model - Model ID\r\n * @returns Promise resolving to context window size in tokens\r\n */\r\nexport async function getModelContextWindow(model: string): Promise<number> {\r\n const config = await getModelConfig(model);\r\n return config?.contextWindow || 2000000; // Default 2M tokens\r\n}\r\n\r\n/**\r\n * Check if model supports thinking (async)\r\n * @param model - Model ID\r\n * @returns Promise resolving to true if model supports thinking\r\n */\r\nexport async function modelSupportsThinking(model: string): Promise<boolean> {\r\n const config = await getModelConfig(model);\r\n return config?.supportsThinking || false;\r\n}\r\n\r\n\r\n"],"mappings":"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","names":[]}
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/config/types.ts"],"sourcesContent":["export interface SubshellConfig {\r\n enabled?: boolean;\r\n ssh?: {\r\n enabled?: boolean;\r\n defaultAuthMethod?: 'password' | 'key';\r\n keyPath?: string;\r\n timeout?: number;\r\n };\r\n wsl?: {\r\n enabled?: boolean;\r\n defaultDistribution?: string;\r\n timeout?: number;\r\n };\r\n docker?: {\r\n enabled?: boolean;\r\n timeout?: number;\r\n };\r\n commandTimeout?: number;\r\n stateQueryTimeout?: number;\r\n reconnectAttempts?: number;\r\n reconnectBackoff?: number;\r\n}\r\n\r\nexport interface Config {\r\n model: string;\r\n modelName?: string; // Display name of the selected model\r\n isLocalModel?: boolean; // True if using a local Ollama model, false for cloud models\r\n autoApprove?: boolean;\r\n subshell?: SubshellConfig;\r\n enhancedQuality?: boolean;\r\n externalThinking?: boolean; // Enable external thinking mode (show <thinking> tags)\r\n autonomousMode?: boolean; // Enable autonomous mode (Silent Operator with task_complete)\r\n aiAutoSuggest?: boolean; // Enable AI-powered command auto-suggestions\r\n}\r\n\r\nexport const DEFAULT_CONFIG: Partial<Config> = {\r\n model: 'gemini-2.5-flash',\r\n autoApprove: false,\r\n enhancedQuality: true, // Enable enhanced quality features by default\r\n externalThinking: false, // Disable external thinking by default (internal reasoning)\r\n autonomousMode: false, // Disable autonomous mode by default (for backward compatibility)\r\n aiAutoSuggest: false, // Disable AI auto-suggest by default\r\n subshell: {\r\n enabled: true,\r\n ssh: {\r\n enabled: true,\r\n defaultAuthMethod: 'key',\r\n timeout: 30000,\r\n },\r\n wsl: {\r\n enabled: true,\r\n timeout: 30000,\r\n },\r\n docker: {\r\n enabled: true,\r\n timeout: 30000,\r\n },\r\n commandTimeout: 30000,\r\n stateQueryTimeout: 10000,\r\n reconnectAttempts: 3,\r\n reconnectBackoff: 1000,\r\n },\r\n};\r\n"],"mappings":"AAmCO,MAAM,iBAAkC;AAAA,EAC7C,OAAO;AAAA,EACP,aAAa;AAAA,EACb,iBAAiB;AAAA;AAAA,EACjB,kBAAkB;AAAA;AAAA,EAClB,gBAAgB;AAAA;AAAA,EAChB,eAAe;AAAA;AAAA,EACf,UAAU;AAAA,IACR,SAAS;AAAA,IACT,KAAK;AAAA,MACH,SAAS;AAAA,MACT,mBAAmB;AAAA,MACnB,SAAS;AAAA,IACX;AAAA,IACA,KAAK;AAAA,MACH,SAAS;AAAA,MACT,SAAS;AAAA,IACX;AAAA,IACA,QAAQ;AAAA,MACN,SAAS;AAAA,MACT,SAAS;AAAA,IACX;AAAA,IACA,gBAAgB;AAAA,IAChB,mBAAmB;AAAA,IACnB,mBAAmB;AAAA,IACnB,kBAAkB;AAAA,EACpB;AACF;","names":[]}
1
+ {"version":3,"sources":["../../src/config/types.ts"],"sourcesContent":["export interface SubshellConfig {\r\n enabled?: boolean;\r\n ssh?: {\r\n enabled?: boolean;\r\n defaultAuthMethod?: 'password' | 'key';\r\n keyPath?: string;\r\n timeout?: number;\r\n };\r\n wsl?: {\r\n enabled?: boolean;\r\n defaultDistribution?: string;\r\n timeout?: number;\r\n };\r\n docker?: {\r\n enabled?: boolean;\r\n timeout?: number;\r\n };\r\n commandTimeout?: number;\r\n stateQueryTimeout?: number;\r\n reconnectAttempts?: number;\r\n reconnectBackoff?: number;\r\n}\r\n\r\nexport interface Config {\r\n model: string;\r\n modelUid?: string; // Unique uid for the selected model entry (e.g. \"claude-opus-4-6-thinking\")\r\n modelName?: string; // Display name of the selected model\r\n isLocalModel?: boolean; // True if using a local Ollama model, false for cloud models\r\n autoApprove?: boolean;\r\n subshell?: SubshellConfig;\r\n enhancedQuality?: boolean;\r\n externalThinking?: boolean; // Enable external thinking mode (show <thinking> tags)\r\n autonomousMode?: boolean; // Enable autonomous mode (Silent Operator with task_complete)\r\n aiAutoSuggest?: boolean; // Enable AI-powered command auto-suggestions\r\n}\r\n\r\nexport const DEFAULT_CONFIG: Partial<Config> = {\r\n model: 'gemini-2.5-flash',\r\n autoApprove: false,\r\n enhancedQuality: true, // Enable enhanced quality features by default\r\n externalThinking: false, // Disable external thinking by default (internal reasoning)\r\n autonomousMode: false, // Disable autonomous mode by default (for backward compatibility)\r\n aiAutoSuggest: false, // Disable AI auto-suggest by default\r\n subshell: {\r\n enabled: true,\r\n ssh: {\r\n enabled: true,\r\n defaultAuthMethod: 'key',\r\n timeout: 30000,\r\n },\r\n wsl: {\r\n enabled: true,\r\n timeout: 30000,\r\n },\r\n docker: {\r\n enabled: true,\r\n timeout: 30000,\r\n },\r\n commandTimeout: 30000,\r\n stateQueryTimeout: 10000,\r\n reconnectAttempts: 3,\r\n reconnectBackoff: 1000,\r\n },\r\n};\r\n"],"mappings":"AAoCO,MAAM,iBAAkC;AAAA,EAC7C,OAAO;AAAA,EACP,aAAa;AAAA,EACb,iBAAiB;AAAA;AAAA,EACjB,kBAAkB;AAAA;AAAA,EAClB,gBAAgB;AAAA;AAAA,EAChB,eAAe;AAAA;AAAA,EACf,UAAU;AAAA,IACR,SAAS;AAAA,IACT,KAAK;AAAA,MACH,SAAS;AAAA,MACT,mBAAmB;AAAA,MACnB,SAAS;AAAA,IACX;AAAA,IACA,KAAK;AAAA,MACH,SAAS;AAAA,MACT,SAAS;AAAA,IACX;AAAA,IACA,QAAQ;AAAA,MACN,SAAS;AAAA,MACT,SAAS;AAAA,IACX;AAAA,IACA,gBAAgB;AAAA,IAChB,mBAAmB;AAAA,IACnB,mBAAmB;AAAA,IACnB,kBAAkB;AAAA,EACpB;AACF;","names":[]}
package/dist/index.js CHANGED
@@ -53,6 +53,7 @@ import { render } from "ink";
53
53
  import { App } from "./ui/components/App.js";
54
54
  import { ErrorBoundary } from "./ui/components/ErrorBoundary.js";
55
55
  import { AuthWelcomeScreen } from "./ui/components/AuthWelcomeScreen.js";
56
+ import { WelcomeBanner } from "./ui/components/WelcomeBanner.js";
56
57
  import { CentaurusCLI } from "./cli-adapter.js";
57
58
  import { apiClient } from "./services/api-client.js";
58
59
  import { authenticateWithGoogle } from "./services/auth-handler.js";
@@ -72,6 +73,9 @@ async function handleAuthentication() {
72
73
  } catch (error) {
73
74
  logWarning("Backend API is not reachable during authentication.");
74
75
  }
76
+ const { renderToString } = await import("./utils/ink-static-render.js");
77
+ const bannerOutput = renderToString(React.createElement(WelcomeBanner));
78
+ process.stdout.write(bannerOutput + "\n");
75
79
  return new Promise((resolve) => {
76
80
  const { waitUntilExit, clear } = render(
77
81
  React.createElement(AuthWelcomeScreen, {
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/index.ts"],"sourcesContent":["#!/usr/bin/env node\r\n\r\n/**\r\n * Centaurus CLI Entry Point\r\n * \r\n * NOTE: NO .env file loading! All configuration is baked in at build time.\r\n * See config/build-config.ts for the compile-time configuration.\r\n */\r\n\r\nimport { fileURLToPath } from 'url';\r\nimport { dirname } from 'path';\r\n\r\nconst __filename = fileURLToPath(import.meta.url);\r\nconst __dirname = dirname(__filename);\r\n\r\n// Import logger early for error handlers\r\nimport { logError, logWarning, logInfo } from './utils/logger.js';\r\nimport { quickLog } from './utils/conversation-logger.js';\r\n\r\n// Global handler for EPIPE errors from MCP servers during shutdown\r\n// This prevents crashes when MCP server processes try to write to closed pipes\r\nprocess.stdout.on('error', (err: NodeJS.ErrnoException) => {\r\n if (err.code === 'EPIPE') {\r\n // Silently ignore EPIPE errors - these happen during graceful shutdown\r\n logWarning(`stdout EPIPE error (suppressed): ${err.message}`);\r\n return;\r\n }\r\n logError('stdout error', err as Error);\r\n});\r\n\r\nprocess.stderr.on('error', (err: NodeJS.ErrnoException) => {\r\n if (err.code === 'EPIPE') {\r\n logWarning(`stderr EPIPE error (suppressed): ${err.message}`);\r\n return;\r\n }\r\n logError('stderr error', err as Error);\r\n});\r\n\r\nprocess.stdin.on('error', (err: NodeJS.ErrnoException) => {\r\n if (err.code === 'EPIPE' || err.code === 'EOF') {\r\n logWarning(`stdin error (suppressed): ${err.code} - ${err.message}`);\r\n return;\r\n }\r\n logError('stdin error', err as Error);\r\n});\r\n\r\n// Handle uncaught errors from child processes (MCP servers)\r\nprocess.on('uncaughtException', (err: NodeJS.ErrnoException) => {\r\n // EPIPE, ECONNRESET, and similar errors from MCP/network operations - log and continue\r\n if (err.code === 'EPIPE' || err.code === 'ECONNRESET' || err.code === 'ENOTCONN') {\r\n logWarning(`Uncaught exception (suppressed): ${err.code} - ${err.message}`);\r\n return;\r\n }\r\n\r\n // Yoga WASM memory access errors - these occur during rapid layout recalculations\r\n // in agent control mode and are non-fatal (the app can continue)\r\n if (err.name === 'RuntimeError' && err.message?.includes('memory access out of bounds')) {\r\n // Log to file only, not terminal\r\n quickLog(`[${new Date().toISOString()}] [WASM] Yoga memory error suppressed: ${err.message}\\n`);\r\n return;\r\n }\r\n\r\n // For critical errors, log and exit\r\n logError('Uncaught exception (fatal)', err as Error);\r\n process.exit(1);\r\n});\r\n\r\n// Handle unhandled promise rejections\r\nprocess.on('unhandledRejection', (reason: any, promise: Promise<any>) => {\r\n const error = reason instanceof Error ? reason : new Error(String(reason));\r\n const errnoError = error as NodeJS.ErrnoException;\r\n\r\n // Suppress common non-fatal errors from MCP/network operations\r\n if (errnoError.code === 'EPIPE' || errnoError.code === 'ECONNRESET' ||\r\n errnoError.code === 'ENOTCONN' || errnoError.code === 'ERR_STREAM_DESTROYED') {\r\n logWarning(`Unhandled rejection (suppressed): ${errnoError.code} - ${error.message}`);\r\n return;\r\n }\r\n\r\n logError('Unhandled rejection', error);\r\n});\r\n\r\nimport React from 'react';\r\nimport { render } from 'ink';\r\nimport { App } from './ui/components/App.js';\r\nimport { ErrorBoundary } from './ui/components/ErrorBoundary.js';\r\nimport { AuthWelcomeScreen } from './ui/components/AuthWelcomeScreen.js';\r\nimport { CentaurusCLI } from './cli-adapter.js';\r\nimport { apiClient } from './services/api-client.js';\r\nimport { authenticateWithGoogle } from './services/auth-handler.js';\r\nimport { Text, Box } from 'ink';\r\nimport { Plan, PlanStep } from './tools/plan-mode.js';\r\n\r\nasync function handleAuthentication(): Promise<boolean> {\r\n // Check if already authenticated and verify session is valid\r\n if (apiClient.isAuthenticated()) {\r\n try {\r\n // Verify the session is actually valid by checking with backend\r\n await apiClient.getCurrentUser();\r\n return true; // Session is valid\r\n } catch (error) {\r\n // Session is invalid/expired, clear it and continue to auth flow\r\n logWarning('Session expired. User needs to sign in again.');\r\n }\r\n }\r\n\r\n // Check if backend is reachable\r\n let backendReachable = false;\r\n try {\r\n await apiClient.healthCheck();\r\n backendReachable = true;\r\n } catch (error) {\r\n // Backend is not reachable - we'll still show auth screen but inform user\r\n logWarning('Backend API is not reachable during authentication.');\r\n }\r\n\r\n // Show authentication welcome screen with picker\r\n return new Promise<boolean>((resolve) => {\r\n const { waitUntilExit, clear } = render(\r\n React.createElement(AuthWelcomeScreen, {\r\n onSignIn: async () => {\r\n clear();\r\n\r\n logInfo('Starting authentication process...');\r\n\r\n // Attempt authentication via web app\r\n // The auth handler will open the browser and wait for callback\r\n const result = await authenticateWithGoogle();\r\n\r\n if (result.success) {\r\n logInfo('Authentication successful!');\r\n resolve(true);\r\n } else {\r\n logWarning(`Authentication failed: ${result.error || 'Unknown error'}`);\r\n process.exit(1);\r\n }\r\n },\r\n onExit: () => {\r\n clear();\r\n logInfo('User exited authentication screen.');\r\n process.exit(0);\r\n },\r\n }),\r\n {\r\n patchConsole: false,\r\n exitOnCtrlC: true,\r\n }\r\n );\r\n });\r\n}\r\n\r\nasync function main() {\r\n // Handle authentication\r\n await handleAuthentication();\r\n\r\n const cli = new CentaurusCLI();\r\n\r\n // Initialize CLI (load config, register tools)\r\n await cli.initialize();\r\n\r\n // Check if configuration migration occurred and show message\r\n const migrationMessage = cli.getMigrationMessage();\r\n if (migrationMessage) {\r\n logInfo(`Configuration migration: ${migrationMessage}`);\r\n\r\n // Wait for user to press Enter, but only if interactive\r\n if (process.stdin.isTTY) {\r\n await new Promise<void>((resolve) => {\r\n const onData = () => {\r\n process.stdin.removeListener('end', onEnd);\r\n resolve();\r\n };\r\n const onEnd = () => {\r\n process.stdin.removeListener('data', onData);\r\n resolve();\r\n };\r\n process.stdin.once('data', onData);\r\n process.stdin.once('end', onEnd);\r\n });\r\n }\r\n }\r\n\r\n // Clear the terminal before starting the UI\r\n process.stdout.write('\\x1b[2J\\x1b[3J\\x1b[H');\r\n\r\n // Render Ink app with error boundary\r\n // patchConsole: false prevents console.log from interfering with Ink's rendering\r\n // This is CRITICAL for Static component to work properly\r\n // debug: false reduces flickering by disabling debug output\r\n const { waitUntilExit } = render(\r\n React.createElement(ErrorBoundary, null,\r\n React.createElement(App, {\r\n onMessage: (msg: string) => cli.handleMessage(msg),\r\n onCancelRequest: () => cli.cancelCurrentRequest(),\r\n initialModel: cli.getModel(),\r\n initialPlanMode: cli.getPlanMode(),\r\n onResponseReceived: (callback: (message: string) => void) => {\r\n cli.setOnResponseCallback(callback);\r\n },\r\n onDirectMessage: (callback: (message: string) => void) => {\r\n cli.setOnDirectMessageCallback(callback);\r\n },\r\n onResponseStream: (callback: (chunk: string) => void) => {\r\n cli.setOnResponseStreamCallback(callback);\r\n },\r\n onClearStreamedResponse: (callback: () => void) => {\r\n cli.setOnClearStreamedResponse(callback);\r\n },\r\n onThoughtStream: (callback: (thought: string) => void) => {\r\n cli.setOnThoughtStreamCallback(callback);\r\n },\r\n onThoughtComplete: (callback: (durationSeconds: number) => void) => {\r\n cli.setOnThoughtCompleteCallback(callback);\r\n },\r\n onPickerSetup: (callback: (options: { message: string; choices: Array<{ label: string; value: string }>; type: 'model' }) => void) => {\r\n cli.setOnShowPickerCallback(callback);\r\n },\r\n onPickerSelection: (selection: string, type: 'model') => {\r\n return cli.handlePickerSelection(selection, type);\r\n },\r\n onToolExecutionUpdate: (callback: (update: { toolName: string; status: 'pending' | 'executing' | 'completed' | 'error'; result?: string; error?: string; arguments?: Record<string, any> }) => void) => {\r\n cli.setOnToolExecutionUpdate(callback);\r\n },\r\n onToolApprovalRequest: (callback: (request: { message: string; risky: boolean; preview?: { type: 'code' | 'diff'; content: string; language?: string }; operationType?: 'write_file' | 'edit_file' | 'execute_command'; operationDetails?: Record<string, any> }) => Promise<boolean>) => {\r\n cli.setOnToolApprovalRequest(callback);\r\n },\r\n onToolStreamingOutput: (callback: (update: { toolName: string; chunk: string; type: 'stdout' | 'stderr' }) => void) => {\r\n cli.setOnToolStreamingOutput(callback);\r\n },\r\n onPlanModeChange: (callback: (planMode: boolean) => void) => {\r\n cli.setOnPlanModeChange(callback);\r\n },\r\n onPlanApprovalRequest: (callback: (plan: Plan) => Promise<boolean>) => {\r\n cli.setOnPlanApprovalRequest(callback);\r\n },\r\n onPlanCreated: (callback: (plan: Plan) => void) => {\r\n cli.setOnPlanCreated(callback);\r\n },\r\n onTaskCompleted: (callback: (task: PlanStep, taskNumber: number, totalTasks: number, completionNote?: string, taskDescription?: string) => void) => {\r\n cli.setOnTaskCompleted(callback);\r\n },\r\n onFileChangeSummary: (callback: (data: { filesChanged: number; insertions: number; deletions: number }) => void) => {\r\n cli.setOnFileChangeSummaryCallback(callback);\r\n },\r\n onCommandModeChange: (callback: (commandMode: boolean) => void) => {\r\n cli.setOnCommandModeChange(callback);\r\n },\r\n onToggleCommandMode: () => {\r\n cli.toggleCommandMode();\r\n },\r\n onBackgroundModeChange: (callback: (backgroundMode: boolean) => void) => {\r\n cli.setOnBackgroundModeChange(callback);\r\n },\r\n onToggleBackgroundMode: () => {\r\n cli.toggleBackgroundMode();\r\n },\r\n onBackgroundTaskCountChange: (callback: (count: number) => void) => {\r\n cli.setOnBackgroundTaskCountChange(callback);\r\n },\r\n onSubAgentCountChange: (callback: (count: number) => void) => {\r\n cli.setOnSubAgentCountChange(callback);\r\n },\r\n onSetAutoModeSetup: (callback: (enabled: boolean) => void) => {\r\n cli.setOnSetAutoMode(callback);\r\n },\r\n\r\n onCwdChange: (callback: (cwd: string) => void) => {\r\n cli.setOnCwdChange(callback);\r\n },\r\n onModelChange: (callback: (modelName: string, contextWindow: number) => void) => {\r\n cli.setOnModelChange(callback);\r\n },\r\n onSubshellContextChange: (callback: (context: any) => void) => {\r\n cli.setOnSubshellContextChange(callback);\r\n },\r\n onPasswordRequest: (callback: (message: string) => Promise<string>) => {\r\n cli.setOnPasswordRequest(callback);\r\n },\r\n onShellInput: (input: string) => {\r\n cli.writeToShellStdin(input);\r\n },\r\n onShellSignal: (signal: string) => {\r\n cli.sendSignalToShell(signal as NodeJS.Signals);\r\n },\r\n onKillProcess: () => {\r\n cli.killCurrentProcess();\r\n },\r\n onInteractiveEditorMode: (callback: (active: boolean, command?: string, cwd?: string, remoteContext?: any, parentContext?: any) => void) => {\r\n cli.setOnInteractiveEditorMode(callback);\r\n },\r\n onConnectionStatusUpdate: (callback: (status: { type: 'ssh' | 'wsl' | 'docker'; status: 'connecting' | 'connected' | 'error' | 'disconnected'; connectionString?: string; error?: string }) => void) => {\r\n cli.setOnConnectionStatusUpdate(callback);\r\n },\r\n onTokenCountUpdate: (callback: (tokens: number) => void) => {\r\n cli.setOnTokenCountUpdate(callback);\r\n },\r\n onContextLimitReached: (callback: (reached: boolean) => void) => {\r\n cli.setOnContextLimitReached(callback);\r\n },\r\n onChatPickerSetup: (callback: (chats: Array<{ id: string; title: string; createdAt: string; updatedAt: string; messageCount: number }>, currentChatId: string | null) => void) => {\r\n cli.setOnShowChatPickerCallback(callback);\r\n },\r\n onChatPickerSelection: (chatId: string) => {\r\n return cli.handleChatPickerSelection(chatId);\r\n },\r\n onChatDeletePickerSetup: (callback: (chats: Array<{ id: string; title: string; createdAt: string; updatedAt: string; messageCount: number }>, currentChatId: string | null) => void) => {\r\n cli.setOnShowChatDeletePickerCallback(callback);\r\n },\r\n onChatDeletePickerSelection: (chatId: string) => {\r\n return cli.handleChatDeleteSelection(chatId);\r\n },\r\n onChatListSetup: (callback: (chats: Array<{ id: string; title: string; createdAt: string; updatedAt: string; messageCount: number }>, currentChatId: string | null) => void) => {\r\n cli.setOnShowChatListCallback(callback);\r\n },\r\n onChatRenamePickerSetup: (callback: (chats: Array<{ id: string; title: string; createdAt: string; updatedAt: string; messageCount: number }>, currentChatId: string | null) => void) => {\r\n cli.setOnShowChatRenamePickerCallback(callback);\r\n },\r\n onChatRename: (chatId: string, newTitle: string) => {\r\n cli.handleChatRename(chatId, newTitle);\r\n },\r\n onRestoreMessagesSetup: (callback: (messages: any[]) => void) => {\r\n cli.setOnRestoreMessagesCallback(callback);\r\n },\r\n onUIMessageHistoryUpdate: (messages: any[]) => {\r\n cli.updateUIMessageHistory(messages);\r\n },\r\n onBackgroundTaskListSetup: (callback: (tasks: any[]) => void) => {\r\n cli.setOnShowBackgroundTaskPickerCallback(callback);\r\n },\r\n onBackgroundTaskSelection: (taskId: string) => {\r\n cli.handleBackgroundTaskSelection(taskId);\r\n },\r\n onBackgroundTaskCancelSetup: (callback: (tasks: any[]) => void) => {\r\n cli.setOnShowBackgroundTaskCancelPickerCallback(callback);\r\n },\r\n onBackgroundTaskCancel: (taskId: string) => {\r\n cli.handleBackgroundTaskCancel(taskId);\r\n },\r\n onBackgroundTaskViewSetup: (callback: (task: any) => void) => {\r\n cli.setOnBackgroundTaskViewCallback(callback);\r\n },\r\n onSessionQuotaUpdate: (callback: (remaining: number, canSend: boolean, timeRemaining: string) => void) => {\r\n cli.setOnSessionQuotaUpdate(callback);\r\n },\r\n // MCP management callbacks\r\n onMCPAddScreenSetup: (callback: () => void) => {\r\n cli.setOnMCPAddScreenSetup(callback);\r\n },\r\n onMCPRemoveScreenSetup: (callback: (servers: Array<{ name: string; command: string; args?: string[]; enabled?: boolean }>) => void) => {\r\n cli.setOnMCPRemoveScreenSetup(callback);\r\n },\r\n onMCPEnableScreenSetup: (callback: (servers: Array<{ name: string; command: string; args?: string[]; enabled?: boolean }>) => void) => {\r\n cli.setOnMCPEnableScreenSetup(callback);\r\n },\r\n onMCPDisableScreenSetup: (callback: (servers: Array<{ name: string; command: string; args?: string[]; enabled?: boolean }>) => void) => {\r\n cli.setOnMCPDisableScreenSetup(callback);\r\n },\r\n onMCPListScreenSetup: (callback: (servers: Array<{ name: string; enabled: boolean; status: 'connected' | 'disconnected' | 'error' | 'connecting'; tools: Array<{ name: string }> }>) => void) => {\r\n cli.setOnMCPListScreenSetup(callback);\r\n },\r\n onMCPAddServer: (config: any) => {\r\n return cli.mcpAddServer(config);\r\n },\r\n onMCPRemoveServer: (name: string) => {\r\n cli.mcpRemoveServer(name);\r\n },\r\n onMCPEnableServer: (name: string) => {\r\n cli.mcpEnableServer(name);\r\n },\r\n onMCPDisableServer: (name: string) => {\r\n cli.mcpDisableServer(name);\r\n },\r\n onMCPValidateConfig: (jsonString: string) => {\r\n return cli.mcpValidateConfig(jsonString);\r\n },\r\n onPromptAnswered: (callback: (shellId: string) => void) => {\r\n cli.setOnPromptAnswered(callback);\r\n },\r\n getMainConversation: () => {\r\n return cli.getConversationHistory();\r\n },\r\n onWarpifySession: (command: string, type: 'ssh' | 'wsl' | 'docker', connectionString?: string) => {\r\n return cli.warpifySession(command, type, connectionString);\r\n },\r\n onAiAutoSuggestChange: (callback: (enabled: boolean) => void) => {\r\n cli.setOnAiAutoSuggestChange(callback);\r\n },\r\n // Workflow callbacks\r\n\r\n onWorkflowCreatorSetup: (callback: (initialSteps?: Array<{ type: 'command' | 'instruction'; content: string }>) => void) => {\r\n cli.setOnShowWorkflowCreator(callback);\r\n },\r\n onWorkflowSave: (name: string, steps: Array<{ type: 'command' | 'instruction'; content: string }>, description?: string) => {\r\n cli.saveWorkflow(name, steps, description);\r\n },\r\n onRulesEditorSetup: (callback: (request: { mode: 'add' | 'edit'; initialName?: string; initialContent?: string }) => void) => {\r\n cli.setOnShowRulesEditor(callback);\r\n },\r\n onRuleSave: (name: string, content: string, previousName?: string) => {\r\n return cli.saveRule(name, content, previousName);\r\n },\r\n getCheckpoints: () => {\r\n return cli.getCheckpointsForAutocomplete();\r\n },\r\n onRevertToCheckpointSetup: (callback: (checkpointIndex: number, prompt: string) => void) => {\r\n cli.setOnRevertToCheckpoint(callback);\r\n },\r\n onSetInputSetup: (callback: (value: string) => void) => {\r\n cli.setOnSetInput(callback);\r\n },\r\n onInterruptQueueUpdate: (callback: (queue: string[]) => void) => {\r\n cli.setOnInterruptQueueUpdate(callback);\r\n },\r\n onQueuedMessageDispatched: (callback: (message: string) => void) => {\r\n cli.setOnQueuedMessageDispatched(callback);\r\n },\r\n onCommandQueueUpdate: (callback: (queue: string[]) => void) => {\r\n cli.setOnCommandQueueUpdate(callback);\r\n },\r\n onQueuedCommandDispatched: (callback: (command: string) => void) => {\r\n cli.setOnQueuedCommandDispatched(callback);\r\n }\r\n })\r\n ),\r\n {\r\n patchConsole: false,\r\n exitOnCtrlC: false,\r\n debug: false\r\n }\r\n );\r\n\r\n // Wait for user to exit, with a fallback mechanism\r\n // If the React component tree crashes silently or the Ink runtime enters an unexpected state,\r\n // waitUntilExit() may never resolve. This fallback ensures we can still exit via signals.\r\n const exitPromise = waitUntilExit();\r\n\r\n const fallbackPromise = new Promise<void>((resolve) => {\r\n const signals: NodeJS.Signals[] = ['SIGINT', 'SIGTERM'];\r\n\r\n // We don't want to immediately exit on the first SIGINT if Ink is healthy \r\n // and processing it to gracefully unmount. We add a short timeout, \r\n // or just rely on the fact that if this resolves first, we exit.\r\n const signalHandler = (sig: string) => {\r\n logWarning(`Received ${sig} (Fallback handler triggered)`);\r\n // Give Ink a small window to gracefully exit first before we force it\r\n setTimeout(() => {\r\n resolve();\r\n }, 500).unref();\r\n\r\n signals.forEach(s => process.removeListener(s, signalHandler));\r\n };\r\n\r\n signals.forEach(sig => process.once(sig, signalHandler));\r\n });\r\n\r\n try {\r\n await Promise.race([exitPromise, fallbackPromise]);\r\n } catch (err) {\r\n logError('Ink render error', err as Error);\r\n } finally {\r\n // Ensure all background processes and timers are terminated\r\n process.exit(0);\r\n }\r\n}\r\n\r\nmain().catch((error) => {\r\n logError('Fatal error in main()', error);\r\n process.exit(1);\r\n});\r\n"],"mappings":"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","names":[]}
1
+ {"version":3,"sources":["../src/index.ts"],"sourcesContent":["#!/usr/bin/env node\r\n\r\n/**\r\n * Centaurus CLI Entry Point\r\n * \r\n * NOTE: NO .env file loading! All configuration is baked in at build time.\r\n * See config/build-config.ts for the compile-time configuration.\r\n */\r\n\r\nimport { fileURLToPath } from 'url';\r\nimport { dirname } from 'path';\r\n\r\nconst __filename = fileURLToPath(import.meta.url);\r\nconst __dirname = dirname(__filename);\r\n\r\n// Import logger early for error handlers\r\nimport { logError, logWarning, logInfo } from './utils/logger.js';\r\nimport { quickLog } from './utils/conversation-logger.js';\r\n\r\n// Global handler for EPIPE errors from MCP servers during shutdown\r\n// This prevents crashes when MCP server processes try to write to closed pipes\r\nprocess.stdout.on('error', (err: NodeJS.ErrnoException) => {\r\n if (err.code === 'EPIPE') {\r\n // Silently ignore EPIPE errors - these happen during graceful shutdown\r\n logWarning(`stdout EPIPE error (suppressed): ${err.message}`);\r\n return;\r\n }\r\n logError('stdout error', err as Error);\r\n});\r\n\r\nprocess.stderr.on('error', (err: NodeJS.ErrnoException) => {\r\n if (err.code === 'EPIPE') {\r\n logWarning(`stderr EPIPE error (suppressed): ${err.message}`);\r\n return;\r\n }\r\n logError('stderr error', err as Error);\r\n});\r\n\r\nprocess.stdin.on('error', (err: NodeJS.ErrnoException) => {\r\n if (err.code === 'EPIPE' || err.code === 'EOF') {\r\n logWarning(`stdin error (suppressed): ${err.code} - ${err.message}`);\r\n return;\r\n }\r\n logError('stdin error', err as Error);\r\n});\r\n\r\n// Handle uncaught errors from child processes (MCP servers)\r\nprocess.on('uncaughtException', (err: NodeJS.ErrnoException) => {\r\n // EPIPE, ECONNRESET, and similar errors from MCP/network operations - log and continue\r\n if (err.code === 'EPIPE' || err.code === 'ECONNRESET' || err.code === 'ENOTCONN') {\r\n logWarning(`Uncaught exception (suppressed): ${err.code} - ${err.message}`);\r\n return;\r\n }\r\n\r\n // Yoga WASM memory access errors - these occur during rapid layout recalculations\r\n // in agent control mode and are non-fatal (the app can continue)\r\n if (err.name === 'RuntimeError' && err.message?.includes('memory access out of bounds')) {\r\n // Log to file only, not terminal\r\n quickLog(`[${new Date().toISOString()}] [WASM] Yoga memory error suppressed: ${err.message}\\n`);\r\n return;\r\n }\r\n\r\n // For critical errors, log and exit\r\n logError('Uncaught exception (fatal)', err as Error);\r\n process.exit(1);\r\n});\r\n\r\n// Handle unhandled promise rejections\r\nprocess.on('unhandledRejection', (reason: any, promise: Promise<any>) => {\r\n const error = reason instanceof Error ? reason : new Error(String(reason));\r\n const errnoError = error as NodeJS.ErrnoException;\r\n\r\n // Suppress common non-fatal errors from MCP/network operations\r\n if (errnoError.code === 'EPIPE' || errnoError.code === 'ECONNRESET' ||\r\n errnoError.code === 'ENOTCONN' || errnoError.code === 'ERR_STREAM_DESTROYED') {\r\n logWarning(`Unhandled rejection (suppressed): ${errnoError.code} - ${error.message}`);\r\n return;\r\n }\r\n\r\n logError('Unhandled rejection', error);\r\n});\r\n\r\nimport React from 'react';\r\nimport { render } from 'ink';\r\nimport { App } from './ui/components/App.js';\r\nimport { ErrorBoundary } from './ui/components/ErrorBoundary.js';\r\nimport { AuthWelcomeScreen } from './ui/components/AuthWelcomeScreen.js';\r\nimport { WelcomeBanner } from './ui/components/WelcomeBanner.js';\r\nimport { CentaurusCLI } from './cli-adapter.js';\r\nimport { apiClient } from './services/api-client.js';\r\nimport { authenticateWithGoogle } from './services/auth-handler.js';\r\nimport { Text, Box } from 'ink';\r\nimport { Plan, PlanStep } from './tools/plan-mode.js';\r\n\r\nasync function handleAuthentication(): Promise<boolean> {\r\n // Check if already authenticated and verify session is valid\r\n if (apiClient.isAuthenticated()) {\r\n try {\r\n // Verify the session is actually valid by checking with backend\r\n await apiClient.getCurrentUser();\r\n return true; // Session is valid\r\n } catch (error) {\r\n // Session is invalid/expired, clear it and continue to auth flow\r\n logWarning('Session expired. User needs to sign in again.');\r\n }\r\n }\r\n\r\n // Check if backend is reachable\r\n let backendReachable = false;\r\n try {\r\n await apiClient.healthCheck();\r\n backendReachable = true;\r\n } catch (error) {\r\n // Backend is not reachable - we'll still show auth screen but inform user\r\n logWarning('Backend API is not reachable during authentication.');\r\n }\r\n\r\n // Show authentication welcome screen with picker\r\n // Pre-render the WelcomeBanner to stdout BEFORE starting the interactive\r\n // Ink instance. This way the banner is static terminal output that won't\r\n // be redrawn when the selector re-renders inside the Ink tree.\r\n const { renderToString } = await import('./utils/ink-static-render.js');\r\n const bannerOutput = renderToString(React.createElement(WelcomeBanner));\r\n process.stdout.write(bannerOutput + '\\n');\r\n\r\n return new Promise<boolean>((resolve) => {\r\n const { waitUntilExit, clear } = render(\r\n React.createElement(AuthWelcomeScreen, {\r\n onSignIn: async () => {\r\n clear();\r\n\r\n logInfo('Starting authentication process...');\r\n\r\n // Attempt authentication via web app\r\n // The auth handler will open the browser and wait for callback\r\n const result = await authenticateWithGoogle();\r\n\r\n if (result.success) {\r\n logInfo('Authentication successful!');\r\n resolve(true);\r\n } else {\r\n logWarning(`Authentication failed: ${result.error || 'Unknown error'}`);\r\n process.exit(1);\r\n }\r\n },\r\n onExit: () => {\r\n clear();\r\n logInfo('User exited authentication screen.');\r\n process.exit(0);\r\n },\r\n }),\r\n {\r\n patchConsole: false,\r\n exitOnCtrlC: true,\r\n }\r\n );\r\n });\r\n}\r\n\r\nasync function main() {\r\n // Handle authentication\r\n await handleAuthentication();\r\n\r\n const cli = new CentaurusCLI();\r\n\r\n // Initialize CLI (load config, register tools)\r\n await cli.initialize();\r\n\r\n // Check if configuration migration occurred and show message\r\n const migrationMessage = cli.getMigrationMessage();\r\n if (migrationMessage) {\r\n logInfo(`Configuration migration: ${migrationMessage}`);\r\n\r\n // Wait for user to press Enter, but only if interactive\r\n if (process.stdin.isTTY) {\r\n await new Promise<void>((resolve) => {\r\n const onData = () => {\r\n process.stdin.removeListener('end', onEnd);\r\n resolve();\r\n };\r\n const onEnd = () => {\r\n process.stdin.removeListener('data', onData);\r\n resolve();\r\n };\r\n process.stdin.once('data', onData);\r\n process.stdin.once('end', onEnd);\r\n });\r\n }\r\n }\r\n\r\n // Clear the terminal before starting the UI\r\n process.stdout.write('\\x1b[2J\\x1b[3J\\x1b[H');\r\n\r\n // Render Ink app with error boundary\r\n // patchConsole: false prevents console.log from interfering with Ink's rendering\r\n // This is CRITICAL for Static component to work properly\r\n // debug: false reduces flickering by disabling debug output\r\n const { waitUntilExit } = render(\r\n React.createElement(ErrorBoundary, null,\r\n React.createElement(App, {\r\n onMessage: (msg: string) => cli.handleMessage(msg),\r\n onCancelRequest: () => cli.cancelCurrentRequest(),\r\n initialModel: cli.getModel(),\r\n initialPlanMode: cli.getPlanMode(),\r\n onResponseReceived: (callback: (message: string) => void) => {\r\n cli.setOnResponseCallback(callback);\r\n },\r\n onDirectMessage: (callback: (message: string) => void) => {\r\n cli.setOnDirectMessageCallback(callback);\r\n },\r\n onResponseStream: (callback: (chunk: string) => void) => {\r\n cli.setOnResponseStreamCallback(callback);\r\n },\r\n onClearStreamedResponse: (callback: () => void) => {\r\n cli.setOnClearStreamedResponse(callback);\r\n },\r\n onThoughtStream: (callback: (thought: string) => void) => {\r\n cli.setOnThoughtStreamCallback(callback);\r\n },\r\n onThoughtComplete: (callback: (durationSeconds: number) => void) => {\r\n cli.setOnThoughtCompleteCallback(callback);\r\n },\r\n onPickerSetup: (callback: (options: { message: string; choices: Array<{ label: string; value: string }>; type: 'model' }) => void) => {\r\n cli.setOnShowPickerCallback(callback);\r\n },\r\n onPickerSelection: (selection: string, type: 'model') => {\r\n return cli.handlePickerSelection(selection, type);\r\n },\r\n onToolExecutionUpdate: (callback: (update: { toolName: string; status: 'pending' | 'executing' | 'completed' | 'error'; result?: string; error?: string; arguments?: Record<string, any> }) => void) => {\r\n cli.setOnToolExecutionUpdate(callback);\r\n },\r\n onToolApprovalRequest: (callback: (request: { message: string; risky: boolean; preview?: { type: 'code' | 'diff'; content: string; language?: string }; operationType?: 'write_file' | 'edit_file' | 'execute_command'; operationDetails?: Record<string, any> }) => Promise<boolean>) => {\r\n cli.setOnToolApprovalRequest(callback);\r\n },\r\n onToolStreamingOutput: (callback: (update: { toolName: string; chunk: string; type: 'stdout' | 'stderr' }) => void) => {\r\n cli.setOnToolStreamingOutput(callback);\r\n },\r\n onPlanModeChange: (callback: (planMode: boolean) => void) => {\r\n cli.setOnPlanModeChange(callback);\r\n },\r\n onPlanApprovalRequest: (callback: (plan: Plan) => Promise<boolean>) => {\r\n cli.setOnPlanApprovalRequest(callback);\r\n },\r\n onPlanCreated: (callback: (plan: Plan) => void) => {\r\n cli.setOnPlanCreated(callback);\r\n },\r\n onTaskCompleted: (callback: (task: PlanStep, taskNumber: number, totalTasks: number, completionNote?: string, taskDescription?: string) => void) => {\r\n cli.setOnTaskCompleted(callback);\r\n },\r\n onFileChangeSummary: (callback: (data: { filesChanged: number; insertions: number; deletions: number }) => void) => {\r\n cli.setOnFileChangeSummaryCallback(callback);\r\n },\r\n onCommandModeChange: (callback: (commandMode: boolean) => void) => {\r\n cli.setOnCommandModeChange(callback);\r\n },\r\n onToggleCommandMode: () => {\r\n cli.toggleCommandMode();\r\n },\r\n onBackgroundModeChange: (callback: (backgroundMode: boolean) => void) => {\r\n cli.setOnBackgroundModeChange(callback);\r\n },\r\n onToggleBackgroundMode: () => {\r\n cli.toggleBackgroundMode();\r\n },\r\n onBackgroundTaskCountChange: (callback: (count: number) => void) => {\r\n cli.setOnBackgroundTaskCountChange(callback);\r\n },\r\n onSubAgentCountChange: (callback: (count: number) => void) => {\r\n cli.setOnSubAgentCountChange(callback);\r\n },\r\n onSetAutoModeSetup: (callback: (enabled: boolean) => void) => {\r\n cli.setOnSetAutoMode(callback);\r\n },\r\n\r\n onCwdChange: (callback: (cwd: string) => void) => {\r\n cli.setOnCwdChange(callback);\r\n },\r\n onModelChange: (callback: (modelName: string, contextWindow: number) => void) => {\r\n cli.setOnModelChange(callback);\r\n },\r\n onSubshellContextChange: (callback: (context: any) => void) => {\r\n cli.setOnSubshellContextChange(callback);\r\n },\r\n onPasswordRequest: (callback: (message: string) => Promise<string>) => {\r\n cli.setOnPasswordRequest(callback);\r\n },\r\n onShellInput: (input: string) => {\r\n cli.writeToShellStdin(input);\r\n },\r\n onShellSignal: (signal: string) => {\r\n cli.sendSignalToShell(signal as NodeJS.Signals);\r\n },\r\n onKillProcess: () => {\r\n cli.killCurrentProcess();\r\n },\r\n onInteractiveEditorMode: (callback: (active: boolean, command?: string, cwd?: string, remoteContext?: any, parentContext?: any) => void) => {\r\n cli.setOnInteractiveEditorMode(callback);\r\n },\r\n onConnectionStatusUpdate: (callback: (status: { type: 'ssh' | 'wsl' | 'docker'; status: 'connecting' | 'connected' | 'error' | 'disconnected'; connectionString?: string; error?: string }) => void) => {\r\n cli.setOnConnectionStatusUpdate(callback);\r\n },\r\n onTokenCountUpdate: (callback: (tokens: number) => void) => {\r\n cli.setOnTokenCountUpdate(callback);\r\n },\r\n onContextLimitReached: (callback: (reached: boolean) => void) => {\r\n cli.setOnContextLimitReached(callback);\r\n },\r\n onChatPickerSetup: (callback: (chats: Array<{ id: string; title: string; createdAt: string; updatedAt: string; messageCount: number }>, currentChatId: string | null) => void) => {\r\n cli.setOnShowChatPickerCallback(callback);\r\n },\r\n onChatPickerSelection: (chatId: string) => {\r\n return cli.handleChatPickerSelection(chatId);\r\n },\r\n onChatDeletePickerSetup: (callback: (chats: Array<{ id: string; title: string; createdAt: string; updatedAt: string; messageCount: number }>, currentChatId: string | null) => void) => {\r\n cli.setOnShowChatDeletePickerCallback(callback);\r\n },\r\n onChatDeletePickerSelection: (chatId: string) => {\r\n return cli.handleChatDeleteSelection(chatId);\r\n },\r\n onChatListSetup: (callback: (chats: Array<{ id: string; title: string; createdAt: string; updatedAt: string; messageCount: number }>, currentChatId: string | null) => void) => {\r\n cli.setOnShowChatListCallback(callback);\r\n },\r\n onChatRenamePickerSetup: (callback: (chats: Array<{ id: string; title: string; createdAt: string; updatedAt: string; messageCount: number }>, currentChatId: string | null) => void) => {\r\n cli.setOnShowChatRenamePickerCallback(callback);\r\n },\r\n onChatRename: (chatId: string, newTitle: string) => {\r\n cli.handleChatRename(chatId, newTitle);\r\n },\r\n onRestoreMessagesSetup: (callback: (messages: any[]) => void) => {\r\n cli.setOnRestoreMessagesCallback(callback);\r\n },\r\n onUIMessageHistoryUpdate: (messages: any[]) => {\r\n cli.updateUIMessageHistory(messages);\r\n },\r\n onBackgroundTaskListSetup: (callback: (tasks: any[]) => void) => {\r\n cli.setOnShowBackgroundTaskPickerCallback(callback);\r\n },\r\n onBackgroundTaskSelection: (taskId: string) => {\r\n cli.handleBackgroundTaskSelection(taskId);\r\n },\r\n onBackgroundTaskCancelSetup: (callback: (tasks: any[]) => void) => {\r\n cli.setOnShowBackgroundTaskCancelPickerCallback(callback);\r\n },\r\n onBackgroundTaskCancel: (taskId: string) => {\r\n cli.handleBackgroundTaskCancel(taskId);\r\n },\r\n onBackgroundTaskViewSetup: (callback: (task: any) => void) => {\r\n cli.setOnBackgroundTaskViewCallback(callback);\r\n },\r\n onSessionQuotaUpdate: (callback: (remaining: number, canSend: boolean, timeRemaining: string) => void) => {\r\n cli.setOnSessionQuotaUpdate(callback);\r\n },\r\n // MCP management callbacks\r\n onMCPAddScreenSetup: (callback: () => void) => {\r\n cli.setOnMCPAddScreenSetup(callback);\r\n },\r\n onMCPRemoveScreenSetup: (callback: (servers: Array<{ name: string; command: string; args?: string[]; enabled?: boolean }>) => void) => {\r\n cli.setOnMCPRemoveScreenSetup(callback);\r\n },\r\n onMCPEnableScreenSetup: (callback: (servers: Array<{ name: string; command: string; args?: string[]; enabled?: boolean }>) => void) => {\r\n cli.setOnMCPEnableScreenSetup(callback);\r\n },\r\n onMCPDisableScreenSetup: (callback: (servers: Array<{ name: string; command: string; args?: string[]; enabled?: boolean }>) => void) => {\r\n cli.setOnMCPDisableScreenSetup(callback);\r\n },\r\n onMCPListScreenSetup: (callback: (servers: Array<{ name: string; enabled: boolean; status: 'connected' | 'disconnected' | 'error' | 'connecting'; tools: Array<{ name: string }> }>) => void) => {\r\n cli.setOnMCPListScreenSetup(callback);\r\n },\r\n onMCPAddServer: (config: any) => {\r\n return cli.mcpAddServer(config);\r\n },\r\n onMCPRemoveServer: (name: string) => {\r\n cli.mcpRemoveServer(name);\r\n },\r\n onMCPEnableServer: (name: string) => {\r\n cli.mcpEnableServer(name);\r\n },\r\n onMCPDisableServer: (name: string) => {\r\n cli.mcpDisableServer(name);\r\n },\r\n onMCPValidateConfig: (jsonString: string) => {\r\n return cli.mcpValidateConfig(jsonString);\r\n },\r\n onPromptAnswered: (callback: (shellId: string) => void) => {\r\n cli.setOnPromptAnswered(callback);\r\n },\r\n getMainConversation: () => {\r\n return cli.getConversationHistory();\r\n },\r\n onWarpifySession: (command: string, type: 'ssh' | 'wsl' | 'docker', connectionString?: string) => {\r\n return cli.warpifySession(command, type, connectionString);\r\n },\r\n onAiAutoSuggestChange: (callback: (enabled: boolean) => void) => {\r\n cli.setOnAiAutoSuggestChange(callback);\r\n },\r\n // Workflow callbacks\r\n\r\n onWorkflowCreatorSetup: (callback: (initialSteps?: Array<{ type: 'command' | 'instruction'; content: string }>) => void) => {\r\n cli.setOnShowWorkflowCreator(callback);\r\n },\r\n onWorkflowSave: (name: string, steps: Array<{ type: 'command' | 'instruction'; content: string }>, description?: string) => {\r\n cli.saveWorkflow(name, steps, description);\r\n },\r\n onRulesEditorSetup: (callback: (request: { mode: 'add' | 'edit'; initialName?: string; initialContent?: string }) => void) => {\r\n cli.setOnShowRulesEditor(callback);\r\n },\r\n onRuleSave: (name: string, content: string, previousName?: string) => {\r\n return cli.saveRule(name, content, previousName);\r\n },\r\n getCheckpoints: () => {\r\n return cli.getCheckpointsForAutocomplete();\r\n },\r\n onRevertToCheckpointSetup: (callback: (checkpointIndex: number, prompt: string) => void) => {\r\n cli.setOnRevertToCheckpoint(callback);\r\n },\r\n onSetInputSetup: (callback: (value: string) => void) => {\r\n cli.setOnSetInput(callback);\r\n },\r\n onInterruptQueueUpdate: (callback: (queue: string[]) => void) => {\r\n cli.setOnInterruptQueueUpdate(callback);\r\n },\r\n onQueuedMessageDispatched: (callback: (message: string) => void) => {\r\n cli.setOnQueuedMessageDispatched(callback);\r\n },\r\n onCommandQueueUpdate: (callback: (queue: string[]) => void) => {\r\n cli.setOnCommandQueueUpdate(callback);\r\n },\r\n onQueuedCommandDispatched: (callback: (command: string) => void) => {\r\n cli.setOnQueuedCommandDispatched(callback);\r\n }\r\n })\r\n ),\r\n {\r\n patchConsole: false,\r\n exitOnCtrlC: false,\r\n debug: false\r\n }\r\n );\r\n\r\n // Wait for user to exit, with a fallback mechanism\r\n // If the React component tree crashes silently or the Ink runtime enters an unexpected state,\r\n // waitUntilExit() may never resolve. This fallback ensures we can still exit via signals.\r\n const exitPromise = waitUntilExit();\r\n\r\n const fallbackPromise = new Promise<void>((resolve) => {\r\n const signals: NodeJS.Signals[] = ['SIGINT', 'SIGTERM'];\r\n\r\n // We don't want to immediately exit on the first SIGINT if Ink is healthy \r\n // and processing it to gracefully unmount. We add a short timeout, \r\n // or just rely on the fact that if this resolves first, we exit.\r\n const signalHandler = (sig: string) => {\r\n logWarning(`Received ${sig} (Fallback handler triggered)`);\r\n // Give Ink a small window to gracefully exit first before we force it\r\n setTimeout(() => {\r\n resolve();\r\n }, 500).unref();\r\n\r\n signals.forEach(s => process.removeListener(s, signalHandler));\r\n };\r\n\r\n signals.forEach(sig => process.once(sig, signalHandler));\r\n });\r\n\r\n try {\r\n await Promise.race([exitPromise, fallbackPromise]);\r\n } catch (err) {\r\n logError('Ink render error', err as Error);\r\n } finally {\r\n // Ensure all background processes and timers are terminated\r\n process.exit(0);\r\n }\r\n}\r\n\r\nmain().catch((error) => {\r\n logError('Fatal error in main()', error);\r\n process.exit(1);\r\n});\r\n"],"mappings":";AASA,SAAS,qBAAqB;AAC9B,SAAS,eAAe;AAExB,MAAM,aAAa,cAAc,YAAY,GAAG;AAChD,MAAM,YAAY,QAAQ,UAAU;AAGpC,SAAS,UAAU,YAAY,eAAe;AAC9C,SAAS,gBAAgB;AAIzB,QAAQ,OAAO,GAAG,SAAS,CAAC,QAA+B;AACzD,MAAI,IAAI,SAAS,SAAS;AAExB,eAAW,oCAAoC,IAAI,OAAO,EAAE;AAC5D;AAAA,EACF;AACA,WAAS,gBAAgB,GAAY;AACvC,CAAC;AAED,QAAQ,OAAO,GAAG,SAAS,CAAC,QAA+B;AACzD,MAAI,IAAI,SAAS,SAAS;AACxB,eAAW,oCAAoC,IAAI,OAAO,EAAE;AAC5D;AAAA,EACF;AACA,WAAS,gBAAgB,GAAY;AACvC,CAAC;AAED,QAAQ,MAAM,GAAG,SAAS,CAAC,QAA+B;AACxD,MAAI,IAAI,SAAS,WAAW,IAAI,SAAS,OAAO;AAC9C,eAAW,6BAA6B,IAAI,IAAI,MAAM,IAAI,OAAO,EAAE;AACnE;AAAA,EACF;AACA,WAAS,eAAe,GAAY;AACtC,CAAC;AAGD,QAAQ,GAAG,qBAAqB,CAAC,QAA+B;AAE9D,MAAI,IAAI,SAAS,WAAW,IAAI,SAAS,gBAAgB,IAAI,SAAS,YAAY;AAChF,eAAW,oCAAoC,IAAI,IAAI,MAAM,IAAI,OAAO,EAAE;AAC1E;AAAA,EACF;AAIA,MAAI,IAAI,SAAS,kBAAkB,IAAI,SAAS,SAAS,6BAA6B,GAAG;AAEvF,aAAS,KAAI,oBAAI,KAAK,GAAE,YAAY,CAAC,0CAA0C,IAAI,OAAO;AAAA,CAAI;AAC9F;AAAA,EACF;AAGA,WAAS,8BAA8B,GAAY;AACnD,UAAQ,KAAK,CAAC;AAChB,CAAC;AAGD,QAAQ,GAAG,sBAAsB,CAAC,QAAa,YAA0B;AACvE,QAAM,QAAQ,kBAAkB,QAAQ,SAAS,IAAI,MAAM,OAAO,MAAM,CAAC;AACzE,QAAM,aAAa;AAGnB,MAAI,WAAW,SAAS,WAAW,WAAW,SAAS,gBACrD,WAAW,SAAS,cAAc,WAAW,SAAS,wBAAwB;AAC9E,eAAW,qCAAqC,WAAW,IAAI,MAAM,MAAM,OAAO,EAAE;AACpF;AAAA,EACF;AAEA,WAAS,uBAAuB,KAAK;AACvC,CAAC;AAED,OAAO,WAAW;AAClB,SAAS,cAAc;AACvB,SAAS,WAAW;AACpB,SAAS,qBAAqB;AAC9B,SAAS,yBAAyB;AAClC,SAAS,qBAAqB;AAC9B,SAAS,oBAAoB;AAC7B,SAAS,iBAAiB;AAC1B,SAAS,8BAA8B;AAIvC,eAAe,uBAAyC;AAEtD,MAAI,UAAU,gBAAgB,GAAG;AAC/B,QAAI;AAEF,YAAM,UAAU,eAAe;AAC/B,aAAO;AAAA,IACT,SAAS,OAAO;AAEd,iBAAW,+CAA+C;AAAA,IAC5D;AAAA,EACF;AAGA,MAAI,mBAAmB;AACvB,MAAI;AACF,UAAM,UAAU,YAAY;AAC5B,uBAAmB;AAAA,EACrB,SAAS,OAAO;AAEd,eAAW,qDAAqD;AAAA,EAClE;AAMA,QAAM,EAAE,eAAe,IAAI,MAAM,OAAO,8BAA8B;AACtE,QAAM,eAAe,eAAe,MAAM,cAAc,aAAa,CAAC;AACtE,UAAQ,OAAO,MAAM,eAAe,IAAI;AAExC,SAAO,IAAI,QAAiB,CAAC,YAAY;AACvC,UAAM,EAAE,eAAe,MAAM,IAAI;AAAA,MAC/B,MAAM,cAAc,mBAAmB;AAAA,QACrC,UAAU,YAAY;AACpB,gBAAM;AAEN,kBAAQ,oCAAoC;AAI5C,gBAAM,SAAS,MAAM,uBAAuB;AAE5C,cAAI,OAAO,SAAS;AAClB,oBAAQ,4BAA4B;AACpC,oBAAQ,IAAI;AAAA,UACd,OAAO;AACL,uBAAW,0BAA0B,OAAO,SAAS,eAAe,EAAE;AACtE,oBAAQ,KAAK,CAAC;AAAA,UAChB;AAAA,QACF;AAAA,QACA,QAAQ,MAAM;AACZ,gBAAM;AACN,kBAAQ,oCAAoC;AAC5C,kBAAQ,KAAK,CAAC;AAAA,QAChB;AAAA,MACF,CAAC;AAAA,MACD;AAAA,QACE,cAAc;AAAA,QACd,aAAa;AAAA,MACf;AAAA,IACF;AAAA,EACF,CAAC;AACH;AAEA,eAAe,OAAO;AAEpB,QAAM,qBAAqB;AAE3B,QAAM,MAAM,IAAI,aAAa;AAG7B,QAAM,IAAI,WAAW;AAGrB,QAAM,mBAAmB,IAAI,oBAAoB;AACjD,MAAI,kBAAkB;AACpB,YAAQ,4BAA4B,gBAAgB,EAAE;AAGtD,QAAI,QAAQ,MAAM,OAAO;AACvB,YAAM,IAAI,QAAc,CAAC,YAAY;AACnC,cAAM,SAAS,MAAM;AACnB,kBAAQ,MAAM,eAAe,OAAO,KAAK;AACzC,kBAAQ;AAAA,QACV;AACA,cAAM,QAAQ,MAAM;AAClB,kBAAQ,MAAM,eAAe,QAAQ,MAAM;AAC3C,kBAAQ;AAAA,QACV;AACA,gBAAQ,MAAM,KAAK,QAAQ,MAAM;AACjC,gBAAQ,MAAM,KAAK,OAAO,KAAK;AAAA,MACjC,CAAC;AAAA,IACH;AAAA,EACF;AAGA,UAAQ,OAAO,MAAM,sBAAsB;AAM3C,QAAM,EAAE,cAAc,IAAI;AAAA,IACxB,MAAM;AAAA,MAAc;AAAA,MAAe;AAAA,MACjC,MAAM,cAAc,KAAK;AAAA,QACvB,WAAW,CAAC,QAAgB,IAAI,cAAc,GAAG;AAAA,QACjD,iBAAiB,MAAM,IAAI,qBAAqB;AAAA,QAChD,cAAc,IAAI,SAAS;AAAA,QAC3B,iBAAiB,IAAI,YAAY;AAAA,QACjC,oBAAoB,CAAC,aAAwC;AAC3D,cAAI,sBAAsB,QAAQ;AAAA,QACpC;AAAA,QACA,iBAAiB,CAAC,aAAwC;AACxD,cAAI,2BAA2B,QAAQ;AAAA,QACzC;AAAA,QACA,kBAAkB,CAAC,aAAsC;AACvD,cAAI,4BAA4B,QAAQ;AAAA,QAC1C;AAAA,QACA,yBAAyB,CAAC,aAAyB;AACjD,cAAI,2BAA2B,QAAQ;AAAA,QACzC;AAAA,QACA,iBAAiB,CAAC,aAAwC;AACxD,cAAI,2BAA2B,QAAQ;AAAA,QACzC;AAAA,QACA,mBAAmB,CAAC,aAAgD;AAClE,cAAI,6BAA6B,QAAQ;AAAA,QAC3C;AAAA,QACA,eAAe,CAAC,aAAsH;AACpI,cAAI,wBAAwB,QAAQ;AAAA,QACtC;AAAA,QACA,mBAAmB,CAAC,WAAmB,SAAkB;AACvD,iBAAO,IAAI,sBAAsB,WAAW,IAAI;AAAA,QAClD;AAAA,QACA,uBAAuB,CAAC,aAAgL;AACtM,cAAI,yBAAyB,QAAQ;AAAA,QACvC;AAAA,QACA,uBAAuB,CAAC,aAAkQ;AACxR,cAAI,yBAAyB,QAAQ;AAAA,QACvC;AAAA,QACA,uBAAuB,CAAC,aAA+F;AACrH,cAAI,yBAAyB,QAAQ;AAAA,QACvC;AAAA,QACA,kBAAkB,CAAC,aAA0C;AAC3D,cAAI,oBAAoB,QAAQ;AAAA,QAClC;AAAA,QACA,uBAAuB,CAAC,aAA+C;AACrE,cAAI,yBAAyB,QAAQ;AAAA,QACvC;AAAA,QACA,eAAe,CAAC,aAAmC;AACjD,cAAI,iBAAiB,QAAQ;AAAA,QAC/B;AAAA,QACA,iBAAiB,CAAC,aAAkI;AAClJ,cAAI,mBAAmB,QAAQ;AAAA,QACjC;AAAA,QACA,qBAAqB,CAAC,aAA8F;AAClH,cAAI,+BAA+B,QAAQ;AAAA,QAC7C;AAAA,QACA,qBAAqB,CAAC,aAA6C;AACjE,cAAI,uBAAuB,QAAQ;AAAA,QACrC;AAAA,QACA,qBAAqB,MAAM;AACzB,cAAI,kBAAkB;AAAA,QACxB;AAAA,QACA,wBAAwB,CAAC,aAAgD;AACvE,cAAI,0BAA0B,QAAQ;AAAA,QACxC;AAAA,QACA,wBAAwB,MAAM;AAC5B,cAAI,qBAAqB;AAAA,QAC3B;AAAA,QACA,6BAA6B,CAAC,aAAsC;AAClE,cAAI,+BAA+B,QAAQ;AAAA,QAC7C;AAAA,QACA,uBAAuB,CAAC,aAAsC;AAC5D,cAAI,yBAAyB,QAAQ;AAAA,QACvC;AAAA,QACA,oBAAoB,CAAC,aAAyC;AAC5D,cAAI,iBAAiB,QAAQ;AAAA,QAC/B;AAAA,QAEA,aAAa,CAAC,aAAoC;AAChD,cAAI,eAAe,QAAQ;AAAA,QAC7B;AAAA,QACA,eAAe,CAAC,aAAiE;AAC/E,cAAI,iBAAiB,QAAQ;AAAA,QAC/B;AAAA,QACA,yBAAyB,CAAC,aAAqC;AAC7D,cAAI,2BAA2B,QAAQ;AAAA,QACzC;AAAA,QACA,mBAAmB,CAAC,aAAmD;AACrE,cAAI,qBAAqB,QAAQ;AAAA,QACnC;AAAA,QACA,cAAc,CAAC,UAAkB;AAC/B,cAAI,kBAAkB,KAAK;AAAA,QAC7B;AAAA,QACA,eAAe,CAAC,WAAmB;AACjC,cAAI,kBAAkB,MAAwB;AAAA,QAChD;AAAA,QACA,eAAe,MAAM;AACnB,cAAI,mBAAmB;AAAA,QACzB;AAAA,QACA,yBAAyB,CAAC,aAAkH;AAC1I,cAAI,2BAA2B,QAAQ;AAAA,QACzC;AAAA,QACA,0BAA0B,CAAC,aAA6K;AACtM,cAAI,4BAA4B,QAAQ;AAAA,QAC1C;AAAA,QACA,oBAAoB,CAAC,aAAuC;AAC1D,cAAI,sBAAsB,QAAQ;AAAA,QACpC;AAAA,QACA,uBAAuB,CAAC,aAAyC;AAC/D,cAAI,yBAAyB,QAAQ;AAAA,QACvC;AAAA,QACA,mBAAmB,CAAC,aAA8J;AAChL,cAAI,4BAA4B,QAAQ;AAAA,QAC1C;AAAA,QACA,uBAAuB,CAAC,WAAmB;AACzC,iBAAO,IAAI,0BAA0B,MAAM;AAAA,QAC7C;AAAA,QACA,yBAAyB,CAAC,aAA8J;AACtL,cAAI,kCAAkC,QAAQ;AAAA,QAChD;AAAA,QACA,6BAA6B,CAAC,WAAmB;AAC/C,iBAAO,IAAI,0BAA0B,MAAM;AAAA,QAC7C;AAAA,QACA,iBAAiB,CAAC,aAA8J;AAC9K,cAAI,0BAA0B,QAAQ;AAAA,QACxC;AAAA,QACA,yBAAyB,CAAC,aAA8J;AACtL,cAAI,kCAAkC,QAAQ;AAAA,QAChD;AAAA,QACA,cAAc,CAAC,QAAgB,aAAqB;AAClD,cAAI,iBAAiB,QAAQ,QAAQ;AAAA,QACvC;AAAA,QACA,wBAAwB,CAAC,aAAwC;AAC/D,cAAI,6BAA6B,QAAQ;AAAA,QAC3C;AAAA,QACA,0BAA0B,CAAC,aAAoB;AAC7C,cAAI,uBAAuB,QAAQ;AAAA,QACrC;AAAA,QACA,2BAA2B,CAAC,aAAqC;AAC/D,cAAI,sCAAsC,QAAQ;AAAA,QACpD;AAAA,QACA,2BAA2B,CAAC,WAAmB;AAC7C,cAAI,8BAA8B,MAAM;AAAA,QAC1C;AAAA,QACA,6BAA6B,CAAC,aAAqC;AACjE,cAAI,4CAA4C,QAAQ;AAAA,QAC1D;AAAA,QACA,wBAAwB,CAAC,WAAmB;AAC1C,cAAI,2BAA2B,MAAM;AAAA,QACvC;AAAA,QACA,2BAA2B,CAAC,aAAkC;AAC5D,cAAI,gCAAgC,QAAQ;AAAA,QAC9C;AAAA,QACA,sBAAsB,CAAC,aAAmF;AACxG,cAAI,wBAAwB,QAAQ;AAAA,QACtC;AAAA;AAAA,QAEA,qBAAqB,CAAC,aAAyB;AAC7C,cAAI,uBAAuB,QAAQ;AAAA,QACrC;AAAA,QACA,wBAAwB,CAAC,aAA8G;AACrI,cAAI,0BAA0B,QAAQ;AAAA,QACxC;AAAA,QACA,wBAAwB,CAAC,aAA8G;AACrI,cAAI,0BAA0B,QAAQ;AAAA,QACxC;AAAA,QACA,yBAAyB,CAAC,aAA8G;AACtI,cAAI,2BAA2B,QAAQ;AAAA,QACzC;AAAA,QACA,sBAAsB,CAAC,aAA0K;AAC/L,cAAI,wBAAwB,QAAQ;AAAA,QACtC;AAAA,QACA,gBAAgB,CAAC,WAAgB;AAC/B,iBAAO,IAAI,aAAa,MAAM;AAAA,QAChC;AAAA,QACA,mBAAmB,CAAC,SAAiB;AACnC,cAAI,gBAAgB,IAAI;AAAA,QAC1B;AAAA,QACA,mBAAmB,CAAC,SAAiB;AACnC,cAAI,gBAAgB,IAAI;AAAA,QAC1B;AAAA,QACA,oBAAoB,CAAC,SAAiB;AACpC,cAAI,iBAAiB,IAAI;AAAA,QAC3B;AAAA,QACA,qBAAqB,CAAC,eAAuB;AAC3C,iBAAO,IAAI,kBAAkB,UAAU;AAAA,QACzC;AAAA,QACA,kBAAkB,CAAC,aAAwC;AACzD,cAAI,oBAAoB,QAAQ;AAAA,QAClC;AAAA,QACA,qBAAqB,MAAM;AACzB,iBAAO,IAAI,uBAAuB;AAAA,QACpC;AAAA,QACA,kBAAkB,CAAC,SAAiB,MAAgC,qBAA8B;AAChG,iBAAO,IAAI,eAAe,SAAS,MAAM,gBAAgB;AAAA,QAC3D;AAAA,QACA,uBAAuB,CAAC,aAAyC;AAC/D,cAAI,yBAAyB,QAAQ;AAAA,QACvC;AAAA;AAAA,QAGA,wBAAwB,CAAC,aAAmG;AAC1H,cAAI,yBAAyB,QAAQ;AAAA,QACvC;AAAA,QACA,gBAAgB,CAAC,MAAc,OAAoE,gBAAyB;AAC1H,cAAI,aAAa,MAAM,OAAO,WAAW;AAAA,QAC3C;AAAA,QACA,oBAAoB,CAAC,aAAyG;AAC5H,cAAI,qBAAqB,QAAQ;AAAA,QACnC;AAAA,QACA,YAAY,CAAC,MAAc,SAAiB,iBAA0B;AACpE,iBAAO,IAAI,SAAS,MAAM,SAAS,YAAY;AAAA,QACjD;AAAA,QACA,gBAAgB,MAAM;AACpB,iBAAO,IAAI,8BAA8B;AAAA,QAC3C;AAAA,QACA,2BAA2B,CAAC,aAAgE;AAC1F,cAAI,wBAAwB,QAAQ;AAAA,QACtC;AAAA,QACA,iBAAiB,CAAC,aAAsC;AACtD,cAAI,cAAc,QAAQ;AAAA,QAC5B;AAAA,QACA,wBAAwB,CAAC,aAAwC;AAC/D,cAAI,0BAA0B,QAAQ;AAAA,QACxC;AAAA,QACA,2BAA2B,CAAC,aAAwC;AAClE,cAAI,6BAA6B,QAAQ;AAAA,QAC3C;AAAA,QACA,sBAAsB,CAAC,aAAwC;AAC7D,cAAI,wBAAwB,QAAQ;AAAA,QACtC;AAAA,QACA,2BAA2B,CAAC,aAAwC;AAClE,cAAI,6BAA6B,QAAQ;AAAA,QAC3C;AAAA,MACF,CAAC;AAAA,IACH;AAAA,IACA;AAAA,MACE,cAAc;AAAA,MACd,aAAa;AAAA,MACb,OAAO;AAAA,IACT;AAAA,EACF;AAKA,QAAM,cAAc,cAAc;AAElC,QAAM,kBAAkB,IAAI,QAAc,CAAC,YAAY;AACrD,UAAM,UAA4B,CAAC,UAAU,SAAS;AAKtD,UAAM,gBAAgB,CAAC,QAAgB;AACrC,iBAAW,YAAY,GAAG,+BAA+B;AAEzD,iBAAW,MAAM;AACf,gBAAQ;AAAA,MACV,GAAG,GAAG,EAAE,MAAM;AAEd,cAAQ,QAAQ,OAAK,QAAQ,eAAe,GAAG,aAAa,CAAC;AAAA,IAC/D;AAEA,YAAQ,QAAQ,SAAO,QAAQ,KAAK,KAAK,aAAa,CAAC;AAAA,EACzD,CAAC;AAED,MAAI;AACF,UAAM,QAAQ,KAAK,CAAC,aAAa,eAAe,CAAC;AAAA,EACnD,SAAS,KAAK;AACZ,aAAS,oBAAoB,GAAY;AAAA,EAC3C,UAAE;AAEA,YAAQ,KAAK,CAAC;AAAA,EAChB;AACF;AAEA,KAAK,EAAE,MAAM,CAAC,UAAU;AACtB,WAAS,yBAAyB,KAAK;AACvC,UAAQ,KAAK,CAAC;AAChB,CAAC;","names":[]}
@@ -37,7 +37,7 @@ class AIServiceClient {
37
37
  * @param mode - Optional mode (default, plan, command)
38
38
  * @yields Stream chunks (text, tool calls, done, or error events)
39
39
  */
40
- async *streamChat(model, messages, tools, environmentContext, mode, thinkingConfig, abortSignal) {
40
+ async *streamChat(model, messages, tools, environmentContext, mode, thinkingConfig, abortSignal, modelName, modelUid) {
41
41
  if (this.isUsingLocalModel()) {
42
42
  const localModel = this.getCurrentModel();
43
43
  const supportsTools = OllamaService.modelSupportsTools(localModel);
@@ -56,11 +56,12 @@ class AIServiceClient {
56
56
  }
57
57
  const payload = {
58
58
  model,
59
+ modelUid,
60
+ modelName,
59
61
  messages,
60
62
  tools,
61
63
  stream: true,
62
64
  clientType: "cli",
63
- // Tell backend this is a CLI request
64
65
  environmentContext,
65
66
  mode,
66
67
  thinkingConfig
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/services/ai-service-client.ts"],"sourcesContent":["/**\r\n * AI Service Client\r\n * \r\n * Handles communication with the backend AI proxy service for streaming\r\n * AI chat requests. Replaces direct Gemini SDK usage in the CLI.\r\n */\r\n\r\nimport { apiClient } from './api-client.js';\r\nimport type { ToolSchema } from '../tools/types.js';\r\nimport type { EnvironmentContext } from '../types/index.js';\r\nimport { readFileSync, existsSync } from 'fs';\r\nimport { join } from 'path';\r\nimport { homedir } from 'os';\r\nimport { IS_DEV_BUILD, DEV_BACKEND_URL, PRODUCTION_BACKEND_URL } from '../config/build-config.js';\r\nimport { logWarning } from '../utils/logger.js';\r\nimport { ollamaService, OllamaChatMessage, OllamaTool, OllamaService, OllamaToolCall } from './ollama-service.js';\r\nimport { quickLog } from '../utils/conversation-logger.js';\r\n\r\n/**\r\n * Message format for AI chat requests\r\n */\r\nexport interface Message {\r\n role: 'system' | 'user' | 'assistant' | 'tool';\r\n content: string;\r\n tool_call_id?: string; // For tool messages - references the tool call being responded to\r\n tool_calls?: ToolCall[]; // For assistant messages - tool calls made by the assistant\r\n thinking?: string; // For assistant messages - thinking/reasoning content from the AI (kept only for most recent turn)\r\n thinkingSignature?: string; // For assistant messages - signature for Claude thinking blocks (must be passed back)\r\n}\r\n\r\n/**\r\n * Tool call from AI model\r\n */\r\nexport interface ToolCall {\r\n id: string;\r\n name: string;\r\n arguments: Record<string, any>;\r\n thoughtSignature?: string; // For Gemini thinking models - must be passed back\r\n}\r\n\r\n/**\r\n * Stream chunk types from backend\r\n */\r\nexport interface TextChunk {\r\n type: 'text';\r\n content: string;\r\n}\r\n\r\nexport interface ToolCallChunk {\r\n type: 'tool_call';\r\n toolCall: ToolCall;\r\n}\r\n\r\nexport interface DoneChunk {\r\n type: 'done';\r\n}\r\n\r\nexport interface ErrorChunk {\r\n type: 'error';\r\n message: string;\r\n code: string;\r\n}\r\n\r\nexport interface ThoughtChunk {\r\n type: 'thought';\r\n content: string;\r\n}\r\n\r\nexport interface ThinkingSignatureChunk {\r\n type: 'thinking_signature';\r\n signature: string;\r\n}\r\n\r\nexport interface FileDescriptionsChunk {\r\n type: 'file_descriptions';\r\n descriptions: Record<string, { description: string; filename: string; mimeType: string }>;\r\n}\r\n\r\nexport type StreamChunk = TextChunk | ToolCallChunk | DoneChunk | ErrorChunk | ThoughtChunk | ThinkingSignatureChunk | FileDescriptionsChunk;\r\n\r\n/**\r\n * Chat request payload\r\n */\r\ninterface ChatRequest {\r\n model: string;\r\n messages: Message[];\r\n tools: ToolSchema[];\r\n stream: boolean;\r\n clientType?: string; // Client type (cli, desktop, etc.)\r\n environmentContext?: EnvironmentContext;\r\n mode?: string;\r\n thinkingConfig?: Record<string, any>;\r\n}\r\n\r\n/**\r\n * AI Service Client for streaming chat requests to backend\r\n */\r\nexport class AIServiceClient {\r\n private baseURL: string;\r\n private maxRetries: number = 5;\r\n private retryDelay: number = 1000; // Start with 1 second\r\n private maxRetryDelay: number = 30000;\r\n private maxRetryAfterDelay: number = 60000;\r\n private highDemandMessage: string = 'Very high demand right now. Please wait and retry after some time.';\r\n\r\n constructor() {\r\n // Don't set baseURL yet - lazy load it when first used\r\n // This allows environment variables to be loaded first\r\n this.baseURL = '';\r\n }\r\n\r\n /**\r\n * Get the base URL for API requests\r\n * Lazy-loaded to ensure environment variables are loaded first\r\n */\r\n private getBaseURL(): string {\r\n if (!this.baseURL) {\r\n // Import build config - values frozen at compile time for security\r\n // SECURITY: This prevents malicious .env files from overriding production URLs\r\n\r\n // Use production URL in production builds, localhost only in dev builds\r\n this.baseURL = IS_DEV_BUILD ? DEV_BACKEND_URL : PRODUCTION_BACKEND_URL;\r\n }\r\n return this.baseURL;\r\n }\r\n\r\n /**\r\n * Stream chat request to backend AI proxy\r\n * \r\n * @param model - The AI model to use (e.g., 'gemini-2.5-flash')\r\n * @param messages - Conversation history including system, user, assistant, and tool messages\r\n * @param tools - Available tool schemas for the AI to use\r\n * @param environmentContext - Optional environment context (OS, shell, cwd, etc.)\r\n * @param mode - Optional mode (default, plan, command)\r\n * @yields Stream chunks (text, tool calls, done, or error events)\r\n */\r\n async *streamChat(\r\n model: string,\r\n messages: Message[],\r\n tools: ToolSchema[],\r\n environmentContext?: EnvironmentContext,\r\n mode?: string,\r\n thinkingConfig?: Record<string, any>,\r\n abortSignal?: AbortSignal\r\n ): AsyncGenerator<StreamChunk, void, unknown> {\r\n // Check if using a local Ollama model\r\n if (this.isUsingLocalModel()) {\r\n const localModel = this.getCurrentModel();\r\n const supportsTools = OllamaService.modelSupportsTools(localModel);\r\n\r\n quickLog(`[${new Date().toISOString()}] [AIServiceClient] Routing to local Ollama model: ${localModel}, supportsTools: ${supportsTools}\\n`);\r\n\r\n // Show warning only if tools are provided but model doesn't support them\r\n if (tools.length > 0 && !supportsTools) {\r\n yield {\r\n type: 'text',\r\n content: `⚠️ Note: Model \"${localModel}\" does not support tool calling. Running in text-only mode.\\n\\n`,\r\n };\r\n }\r\n\r\n // Route to local Ollama with tools\r\n yield* this.streamLocalChat(localModel, messages, tools);\r\n return;\r\n }\r\n\r\n // Build request payload for cloud backend\r\n const payload: ChatRequest = {\r\n model,\r\n messages,\r\n tools,\r\n stream: true,\r\n clientType: 'cli', // Tell backend this is a CLI request\r\n environmentContext,\r\n mode,\r\n thinkingConfig,\r\n };\r\n\r\n // Get authentication token from api client\r\n if (!apiClient.isAuthenticated()) {\r\n yield {\r\n type: 'error',\r\n message: 'Authentication required. Please sign in.',\r\n code: 'AUTH_REQUIRED',\r\n };\r\n return;\r\n }\r\n\r\n // Retry logic for transient errors\r\n let lastError: ErrorChunk | null = null;\r\n\r\n for (let attempt = 0; attempt < this.maxRetries; attempt++) {\r\n try {\r\n // Make fetch request to backend AI endpoint\r\n // NOTE: We intentionally do NOT set a timeout here because:\r\n // 1. Tool execution happens during streaming (in-stream execution)\r\n // 2. Interactive commands may require user input for arbitrary duration\r\n // 3. The only timeout should be explicit user cancellation via abortSignal\r\n // Connection issues will manifest as network errors, not timeouts\r\n\r\n const response = await fetch(`${this.getBaseURL()}/chat/completions`, {\r\n method: 'POST',\r\n headers: {\r\n 'Content-Type': 'application/json',\r\n 'Authorization': `Bearer ${this.getSessionToken()}`,\r\n },\r\n body: JSON.stringify(payload),\r\n signal: abortSignal, // Only abort when user explicitly cancels\r\n });\r\n\r\n\r\n\r\n // Check for HTTP errors\r\n if (!response.ok) {\r\n const errorText = await response.text();\r\n const parsedHttpError = this.parseHttpError(response.status, response.statusText, errorText);\r\n\r\n // Handle specific error codes\r\n if (response.status === 401) {\r\n yield {\r\n type: 'error',\r\n message: 'Session expired. Please sign in again.',\r\n code: 'AUTH_REQUIRED',\r\n };\r\n return;\r\n }\r\n\r\n lastError = parsedHttpError;\r\n\r\n if (this.isRetryableError(parsedHttpError.code, parsedHttpError.message, response.status) && attempt < this.maxRetries - 1) {\r\n const retryAfterMs = this.getRetryAfterMs(response.headers);\r\n await this.sleep(this.getRetryDelayMs(attempt, retryAfterMs));\r\n continue;\r\n }\r\n\r\n if (this.isRateLimitError(parsedHttpError.code, parsedHttpError.message, response.status)) {\r\n yield this.createHighDemandError();\r\n return;\r\n }\r\n\r\n yield parsedHttpError;\r\n return;\r\n }\r\n\r\n // Parse SSE stream\r\n // We iterate manually to catch retryable errors from the stream\r\n let contentReceived = false;\r\n let shouldRetry = false;\r\n\r\n for await (const chunk of this.parseSSEStream(response.body)) {\r\n if (chunk.type === 'error') {\r\n const normalizedChunk = this.normalizeErrorChunk(chunk);\r\n\r\n // Check if this is a retryable error (like rate limit) AND we haven't sent partial content yet\r\n if (this.isRetryableError(normalizedChunk.code, normalizedChunk.message) && !contentReceived) {\r\n lastError = normalizedChunk;\r\n shouldRetry = true;\r\n break; // Break the stream loop to trigger retry\r\n }\r\n\r\n // Not retryable or content already sent - yield error and stop\r\n yield normalizedChunk;\r\n return;\r\n }\r\n\r\n // Mark that we've received content\r\n if (chunk.type !== 'done' && chunk.type !== 'thought' && chunk.type !== 'thinking_signature' && chunk.type !== 'file_descriptions') {\r\n contentReceived = true;\r\n }\r\n\r\n yield chunk;\r\n }\r\n\r\n // If stream finished cleanly (without error chunk), we are done\r\n if (!shouldRetry) {\r\n return;\r\n }\r\n\r\n // If we need to retry, check attempts and wait\r\n if (attempt < this.maxRetries - 1) {\r\n // We're going to retry, so we need to wait for the backoff period\r\n await this.sleep(this.getRetryDelayMs(attempt));\r\n continue;\r\n }\r\n\r\n // If we've exhausted retries, we'll fall through to the end of the loop \r\n // and yield the lastError below\r\n\r\n } catch (error: any) {\r\n lastError = this.normalizeThrownError(error);\r\n const wasIntentionalAbort = (error?.name === 'AbortError' || error?.name === 'TimeoutError') && !!abortSignal?.aborted;\r\n\r\n if (wasIntentionalAbort) {\r\n break;\r\n }\r\n\r\n // Retry for transient errors\r\n if (this.isRetryableError(lastError.code, lastError.message) && attempt < this.maxRetries - 1) {\r\n await this.sleep(this.getRetryDelayMs(attempt));\r\n continue;\r\n }\r\n\r\n // If not retryable or max retries reached, yield error and return\r\n break;\r\n }\r\n }\r\n\r\n // If we get here, we've exhausted retries\r\n if (lastError) {\r\n if (this.isRateLimitError(lastError.code, lastError.message)) {\r\n yield this.createHighDemandError();\r\n return;\r\n }\r\n yield lastError;\r\n }\r\n }\r\n\r\n /**\r\n * Create a normalized high-demand error response.\r\n */\r\n private createHighDemandError(): ErrorChunk {\r\n return {\r\n type: 'error',\r\n message: this.highDemandMessage,\r\n code: 'RATE_LIMIT',\r\n };\r\n }\r\n\r\n /**\r\n * Parse backend HTTP error body into a normalized error chunk.\r\n */\r\n private parseHttpError(status: number, statusText: string, errorText: string): ErrorChunk {\r\n let errorMessage = `HTTP ${status}: ${statusText}`;\r\n let errorCode = 'HTTP_ERROR';\r\n\r\n if (errorText) {\r\n try {\r\n const parsed = JSON.parse(errorText);\r\n const rootError = Array.isArray(parsed) ? parsed[0]?.error ?? parsed[0] : parsed?.error ?? parsed;\r\n\r\n if (rootError) {\r\n if (typeof rootError.message === 'string' && rootError.message.trim()) {\r\n errorMessage = rootError.message;\r\n }\r\n if (typeof rootError.code === 'string' || typeof rootError.code === 'number') {\r\n errorCode = String(rootError.code);\r\n } else if (typeof rootError.status === 'string') {\r\n errorCode = rootError.status;\r\n }\r\n }\r\n } catch {\r\n errorMessage = errorText;\r\n }\r\n }\r\n\r\n if (status === 504) {\r\n return {\r\n type: 'error',\r\n message: 'Request timed out. Please try again.',\r\n code: 'TIMEOUT',\r\n };\r\n }\r\n\r\n if (this.isRateLimitError(errorCode, errorMessage, status)) {\r\n return this.createHighDemandError();\r\n }\r\n\r\n return {\r\n type: 'error',\r\n message: errorMessage,\r\n code: errorCode,\r\n };\r\n }\r\n\r\n /**\r\n * Normalize streamed error chunks to stable internal codes/messages.\r\n */\r\n private normalizeErrorChunk(chunk: ErrorChunk): ErrorChunk {\r\n const normalizedCode = chunk.code ? String(chunk.code) : 'UNKNOWN_ERROR';\r\n const normalizedMessage = chunk.message || 'Unknown error occurred';\r\n\r\n if (this.isRateLimitError(normalizedCode, normalizedMessage)) {\r\n return this.createHighDemandError();\r\n }\r\n\r\n if (normalizedCode === '504') {\r\n return {\r\n type: 'error',\r\n message: 'Request timed out. Please try again.',\r\n code: 'TIMEOUT',\r\n };\r\n }\r\n\r\n return {\r\n type: 'error',\r\n message: normalizedMessage,\r\n code: normalizedCode,\r\n };\r\n }\r\n\r\n /**\r\n * Normalize thrown errors from fetch/network/runtime paths.\r\n */\r\n private normalizeThrownError(error: any): ErrorChunk {\r\n const rawMessage = error?.message || 'Unknown error occurred';\r\n const rawCode = error?.code ? String(error.code) : 'UNKNOWN_ERROR';\r\n\r\n if (this.isRateLimitError(rawCode, rawMessage, error?.status)) {\r\n return this.createHighDemandError();\r\n }\r\n\r\n if (error?.name === 'TypeError' && rawMessage.includes('fetch')) {\r\n return {\r\n type: 'error',\r\n message: 'Backend service is unreachable. Please check your connection.',\r\n code: 'NETWORK_ERROR',\r\n };\r\n }\r\n\r\n if (error?.name === 'AbortError' || error?.name === 'TimeoutError') {\r\n return {\r\n type: 'error',\r\n message: 'Request timed out. Please try again.',\r\n code: 'TIMEOUT',\r\n };\r\n }\r\n\r\n return {\r\n type: 'error',\r\n message: rawMessage,\r\n code: rawCode,\r\n };\r\n }\r\n\r\n /**\r\n * Check if an error is a rate-limit/resource-exhausted signal.\r\n */\r\n private isRateLimitError(code?: string, message: string = '', status?: number): boolean {\r\n const normalizedCode = (code || '').toString().toLowerCase();\r\n const normalizedMessage = message.toLowerCase();\r\n\r\n return status === 429 ||\r\n normalizedCode === '429' ||\r\n normalizedCode === '8' ||\r\n normalizedCode === 'rate_limit' ||\r\n normalizedCode === 'resource_exhausted' ||\r\n normalizedMessage.includes('rate limit') ||\r\n normalizedMessage.includes('too many requests') ||\r\n normalizedMessage.includes('quota') ||\r\n normalizedMessage.includes('resource exhausted') ||\r\n normalizedMessage.includes('resource_exhausted') ||\r\n normalizedMessage.includes('maas api returned 429');\r\n }\r\n\r\n /**\r\n * Check if HTTP status is retryable.\r\n */\r\n private isRetryableStatus(status?: number): boolean {\r\n if (typeof status !== 'number') {\r\n return false;\r\n }\r\n return [408, 425, 429, 500, 502, 503, 504].includes(status);\r\n }\r\n\r\n /**\r\n * Check if an error should be retried.\r\n */\r\n private isRetryableError(code: string, message: string = '', status?: number): boolean {\r\n if (this.isRateLimitError(code, message, status)) {\r\n return true;\r\n }\r\n\r\n if (this.isRetryableStatus(status)) {\r\n return true;\r\n }\r\n\r\n const retryableCodes = ['NETWORK_ERROR', 'TIMEOUT', 'UNKNOWN_ERROR', 'RATE_LIMIT'];\r\n if (retryableCodes.includes(code)) {\r\n return true;\r\n }\r\n\r\n const normalizedMessage = message.toLowerCase();\r\n return normalizedMessage.includes('timeout') ||\r\n normalizedMessage.includes('timed out') ||\r\n normalizedMessage.includes('temporarily unavailable') ||\r\n normalizedMessage.includes('network error');\r\n }\r\n\r\n /**\r\n * Parse Retry-After from response headers.\r\n */\r\n private getRetryAfterMs(headers: Headers): number | undefined {\r\n const retryAfterValue = headers.get('retry-after');\r\n if (!retryAfterValue) {\r\n return undefined;\r\n }\r\n\r\n const seconds = Number(retryAfterValue);\r\n if (Number.isFinite(seconds) && seconds >= 0) {\r\n return seconds * 1000;\r\n }\r\n\r\n const retryAfterDate = Date.parse(retryAfterValue);\r\n if (!Number.isNaN(retryAfterDate)) {\r\n return Math.max(0, retryAfterDate - Date.now());\r\n }\r\n\r\n return undefined;\r\n }\r\n\r\n /**\r\n * Compute retry delay with exponential backoff + jitter.\r\n */\r\n private getRetryDelayMs(attempt: number, retryAfterMs?: number): number {\r\n const exponentialDelay = this.retryDelay * Math.pow(2, attempt);\r\n const cappedDelay = Math.min(exponentialDelay, this.maxRetryDelay);\r\n const jitteredDelay = Math.round(Math.min(cappedDelay * (0.5 + Math.random()), this.maxRetryDelay));\r\n\r\n if (retryAfterMs === undefined) {\r\n return jitteredDelay;\r\n }\r\n\r\n const cappedRetryAfter = Math.min(Math.max(0, retryAfterMs), this.maxRetryAfterDelay);\r\n return Math.max(jitteredDelay, cappedRetryAfter);\r\n }\r\n\r\n /**\r\n * Sleep for specified milliseconds\r\n */\r\n private sleep(ms: number): Promise<void> {\r\n return new Promise(resolve => setTimeout(resolve, ms));\r\n }\r\n\r\n /**\r\n * Get session token from apiClient\r\n * This is a workaround since sessionToken is private\r\n */\r\n private getSessionToken(): string {\r\n // Read session token from the same location apiClient uses\r\n const configPath = join(homedir(), '.centaurus', 'session.json');\r\n\r\n try {\r\n if (existsSync(configPath)) {\r\n const data = readFileSync(configPath, 'utf-8');\r\n const session = JSON.parse(data);\r\n return session.sessionToken || '';\r\n }\r\n } catch (error) {\r\n // Return empty string if unable to read\r\n }\r\n\r\n return '';\r\n }\r\n\r\n /**\r\n * Check if the current configuration is using a local Ollama model\r\n */\r\n isUsingLocalModel(): boolean {\r\n const configPath = join(homedir(), '.centaurus', 'config.json');\r\n try {\r\n if (existsSync(configPath)) {\r\n const data = readFileSync(configPath, 'utf-8');\r\n const config = JSON.parse(data);\r\n return config.isLocalModel === true;\r\n }\r\n } catch (error) {\r\n // Return false if unable to read\r\n }\r\n return false;\r\n }\r\n\r\n /**\r\n * Get the current model name from config\r\n */\r\n private getCurrentModel(): string {\r\n const configPath = join(homedir(), '.centaurus', 'config.json');\r\n try {\r\n if (existsSync(configPath)) {\r\n const data = readFileSync(configPath, 'utf-8');\r\n const config = JSON.parse(data);\r\n return config.model || 'gemini-2.5-flash';\r\n }\r\n } catch (error) {\r\n // Return default if unable to read\r\n }\r\n return 'gemini-2.5-flash';\r\n }\r\n\r\n /**\r\n * Convert internal message format to Ollama format (with tool support)\r\n */\r\n private convertToOllamaMessages(messages: Message[]): OllamaChatMessage[] {\r\n return messages.map(msg => {\r\n if (msg.role === 'tool') {\r\n // Tool results - convert to Ollama's tool message format\r\n return {\r\n role: 'tool' as const,\r\n content: msg.content,\r\n tool_name: msg.tool_call_id || 'unknown', // Use tool_call_id as tool_name\r\n };\r\n }\r\n\r\n if (msg.role === 'assistant' && msg.tool_calls?.length) {\r\n // Assistant message with tool calls\r\n return {\r\n role: 'assistant' as const,\r\n content: msg.content || '',\r\n tool_calls: msg.tool_calls.map(tc => ({\r\n function: {\r\n name: tc.name,\r\n arguments: tc.arguments,\r\n }\r\n })),\r\n };\r\n }\r\n\r\n // Regular message\r\n return {\r\n role: msg.role as 'system' | 'user' | 'assistant',\r\n content: msg.content,\r\n };\r\n });\r\n }\r\n\r\n /**\r\n * Convert ToolSchema array to Ollama tool format\r\n */\r\n private convertToolsToOllamaFormat(tools: ToolSchema[]): OllamaTool[] {\r\n return tools.map(tool => ({\r\n type: 'function' as const,\r\n function: {\r\n name: tool.name,\r\n description: tool.description,\r\n parameters: {\r\n type: 'object' as const,\r\n properties: tool.parameters.properties,\r\n required: tool.parameters.required,\r\n },\r\n },\r\n }));\r\n }\r\n\r\n /**\r\n * Stream chat request to local Ollama instance with tool calling support\r\n * \r\n * @param model - The local Ollama model to use (e.g., 'llama3.2:latest')\r\n * @param messages - Conversation history\r\n * @param tools - Available tools (will be converted to Ollama format)\r\n * @yields Stream chunks (text, tool_call, or done events)\r\n */\r\n async *streamLocalChat(\r\n model: string,\r\n messages: Message[],\r\n tools: ToolSchema[] = []\r\n ): AsyncGenerator<StreamChunk, void, unknown> {\r\n try {\r\n const supportsTools = OllamaService.modelSupportsTools(model);\r\n const effectiveTools = supportsTools ? tools : [];\r\n\r\n quickLog(`[${new Date().toISOString()}] [AIServiceClient] Starting local chat with model: ${model}, tools: ${effectiveTools.length}, supportsTools: ${supportsTools}\\n`);\r\n\r\n // Convert messages to Ollama format\r\n const ollamaMessages = this.convertToOllamaMessages(messages);\r\n\r\n // Convert tools to Ollama format if model supports them\r\n const ollamaTools = effectiveTools.length > 0\r\n ? this.convertToolsToOllamaFormat(effectiveTools)\r\n : undefined;\r\n\r\n // Send request to Ollama with tools\r\n const response = await ollamaService.sendChatMessage(model, ollamaMessages, ollamaTools);\r\n\r\n // Check for tool calls in response\r\n if (response.message?.tool_calls?.length) {\r\n quickLog(`[${new Date().toISOString()}] [AIServiceClient] Received ${response.message.tool_calls.length} tool calls from Ollama\\n`);\r\n\r\n // Yield each tool call\r\n for (const toolCall of response.message.tool_calls) {\r\n yield {\r\n type: 'tool_call',\r\n toolCall: {\r\n id: `ollama-${Date.now()}-${toolCall.function.name}`, // Generate unique ID\r\n name: toolCall.function.name,\r\n arguments: toolCall.function.arguments,\r\n },\r\n };\r\n }\r\n\r\n // Signal completion (tool execution will happen in cli-adapter)\r\n yield { type: 'done' };\r\n return;\r\n }\r\n\r\n // No tool calls - yield text response\r\n if (response.message.content) {\r\n yield {\r\n type: 'text',\r\n content: response.message.content,\r\n };\r\n }\r\n\r\n // Signal completion\r\n yield { type: 'done' };\r\n\r\n quickLog(`[${new Date().toISOString()}] [AIServiceClient] Local chat completed\\n`);\r\n } catch (error: any) {\r\n quickLog(`[${new Date().toISOString()}] [AIServiceClient] Local chat error: ${error.message}\\n`);\r\n yield {\r\n type: 'error',\r\n message: error.message || 'Failed to communicate with Ollama',\r\n code: 'OLLAMA_ERROR',\r\n };\r\n }\r\n }\r\n\r\n /**\r\n * Parse Server-Sent Events stream from response body\r\n * \r\n * @param body - ReadableStream from fetch response\r\n * @yields Parsed stream chunks\r\n */\r\n private async *parseSSEStream(\r\n body: ReadableStream<Uint8Array>\r\n ): AsyncGenerator<StreamChunk, void, unknown> {\r\n const reader = body.getReader();\r\n const decoder = new TextDecoder();\r\n let buffer = '';\r\n\r\n try {\r\n while (true) {\r\n const { done, value } = await reader.read();\r\n\r\n if (done) {\r\n // Process any remaining data in buffer before exiting\r\n if (buffer.trim()) {\r\n // Split remaining buffer and process\r\n const remainingLines = buffer.split('\\n');\r\n for (const line of remainingLines) {\r\n if (line.startsWith('data: ')) {\r\n const dataStr = line.slice(6);\r\n if (dataStr.trim()) {\r\n try {\r\n const chunk = JSON.parse(dataStr) as StreamChunk;\r\n yield chunk;\r\n } catch (error) {\r\n // Skip malformed JSON\r\n logWarning(`Failed to parse SSE data (in buffer): ${dataStr}`);\r\n }\r\n }\r\n }\r\n }\r\n }\r\n break;\r\n }\r\n\r\n // Decode chunk and add to buffer\r\n buffer += decoder.decode(value, { stream: true });\r\n\r\n // Process complete lines in buffer\r\n const lines = buffer.split('\\n');\r\n\r\n // Keep the last incomplete line in buffer\r\n buffer = lines.pop() || '';\r\n\r\n for (const line of lines) {\r\n // SSE format: \"data: {json}\"\r\n if (line.startsWith('data: ')) {\r\n const dataStr = line.slice(6); // Remove \"data: \" prefix\r\n\r\n // Skip empty data lines\r\n if (!dataStr.trim()) {\r\n continue;\r\n }\r\n\r\n try {\r\n const chunk = JSON.parse(dataStr) as StreamChunk;\r\n yield chunk;\r\n\r\n // Stop if we receive a done or error event\r\n if (chunk.type === 'done' || chunk.type === 'error') {\r\n return;\r\n }\r\n } catch (error) {\r\n // Skip malformed JSON\r\n logWarning(`Failed to parse SSE data: ${dataStr}`);\r\n }\r\n }\r\n // SSE event type line: \"event: chunk\"\r\n // We don't need to process these separately since the data contains the type\r\n }\r\n }\r\n } finally {\r\n reader.releaseLock();\r\n }\r\n }\r\n}\r\n\r\n// Export singleton instance\r\nexport const aiServiceClient = new AIServiceClient();\r\n"],"mappings":"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","names":[]}
1
+ {"version":3,"sources":["../../src/services/ai-service-client.ts"],"sourcesContent":["/**\r\n * AI Service Client\r\n * \r\n * Handles communication with the backend AI proxy service for streaming\r\n * AI chat requests. Replaces direct Gemini SDK usage in the CLI.\r\n */\r\n\r\nimport { apiClient } from './api-client.js';\r\nimport type { ToolSchema } from '../tools/types.js';\r\nimport type { EnvironmentContext } from '../types/index.js';\r\nimport { readFileSync, existsSync } from 'fs';\r\nimport { join } from 'path';\r\nimport { homedir } from 'os';\r\nimport { IS_DEV_BUILD, DEV_BACKEND_URL, PRODUCTION_BACKEND_URL } from '../config/build-config.js';\r\nimport { logWarning } from '../utils/logger.js';\r\nimport { ollamaService, OllamaChatMessage, OllamaTool, OllamaService, OllamaToolCall } from './ollama-service.js';\r\nimport { quickLog } from '../utils/conversation-logger.js';\r\n\r\n/**\r\n * Message format for AI chat requests\r\n */\r\nexport interface Message {\r\n role: 'system' | 'user' | 'assistant' | 'tool';\r\n content: string;\r\n tool_call_id?: string; // For tool messages - references the tool call being responded to\r\n tool_calls?: ToolCall[]; // For assistant messages - tool calls made by the assistant\r\n thinking?: string; // For assistant messages - thinking/reasoning content from the AI (kept only for most recent turn)\r\n thinkingSignature?: string; // For assistant messages - signature for Claude thinking blocks (must be passed back)\r\n}\r\n\r\n/**\r\n * Tool call from AI model\r\n */\r\nexport interface ToolCall {\r\n id: string;\r\n name: string;\r\n arguments: Record<string, any>;\r\n thoughtSignature?: string; // For Gemini thinking models - must be passed back\r\n}\r\n\r\n/**\r\n * Stream chunk types from backend\r\n */\r\nexport interface TextChunk {\r\n type: 'text';\r\n content: string;\r\n}\r\n\r\nexport interface ToolCallChunk {\r\n type: 'tool_call';\r\n toolCall: ToolCall;\r\n}\r\n\r\nexport interface DoneChunk {\r\n type: 'done';\r\n}\r\n\r\nexport interface ErrorChunk {\r\n type: 'error';\r\n message: string;\r\n code: string;\r\n}\r\n\r\nexport interface ThoughtChunk {\r\n type: 'thought';\r\n content: string;\r\n}\r\n\r\nexport interface ThinkingSignatureChunk {\r\n type: 'thinking_signature';\r\n signature: string;\r\n}\r\n\r\nexport interface FileDescriptionsChunk {\r\n type: 'file_descriptions';\r\n descriptions: Record<string, { description: string; filename: string; mimeType: string }>;\r\n}\r\n\r\nexport type StreamChunk = TextChunk | ToolCallChunk | DoneChunk | ErrorChunk | ThoughtChunk | ThinkingSignatureChunk | FileDescriptionsChunk;\r\n\r\n/**\r\n * Chat request payload\r\n */\r\ninterface ChatRequest {\r\n model: string;\r\n modelUid?: string; // Unique uid per model entry — used by backend to find exact display name\r\n modelName?: string; // Display name (legacy fallback)\r\n messages: Message[];\r\n tools: ToolSchema[];\r\n stream: boolean;\r\n clientType?: string;\r\n environmentContext?: EnvironmentContext;\r\n mode?: string;\r\n thinkingConfig?: Record<string, any>;\r\n}\r\n\r\n/**\r\n * AI Service Client for streaming chat requests to backend\r\n */\r\nexport class AIServiceClient {\r\n private baseURL: string;\r\n private maxRetries: number = 5;\r\n private retryDelay: number = 1000; // Start with 1 second\r\n private maxRetryDelay: number = 30000;\r\n private maxRetryAfterDelay: number = 60000;\r\n private highDemandMessage: string = 'Very high demand right now. Please wait and retry after some time.';\r\n\r\n constructor() {\r\n // Don't set baseURL yet - lazy load it when first used\r\n // This allows environment variables to be loaded first\r\n this.baseURL = '';\r\n }\r\n\r\n /**\r\n * Get the base URL for API requests\r\n * Lazy-loaded to ensure environment variables are loaded first\r\n */\r\n private getBaseURL(): string {\r\n if (!this.baseURL) {\r\n // Import build config - values frozen at compile time for security\r\n // SECURITY: This prevents malicious .env files from overriding production URLs\r\n\r\n // Use production URL in production builds, localhost only in dev builds\r\n this.baseURL = IS_DEV_BUILD ? DEV_BACKEND_URL : PRODUCTION_BACKEND_URL;\r\n }\r\n return this.baseURL;\r\n }\r\n\r\n /**\r\n * Stream chat request to backend AI proxy\r\n * \r\n * @param model - The AI model to use (e.g., 'gemini-2.5-flash')\r\n * @param messages - Conversation history including system, user, assistant, and tool messages\r\n * @param tools - Available tool schemas for the AI to use\r\n * @param environmentContext - Optional environment context (OS, shell, cwd, etc.)\r\n * @param mode - Optional mode (default, plan, command)\r\n * @yields Stream chunks (text, tool calls, done, or error events)\r\n */\r\n async *streamChat(\r\n model: string,\r\n messages: Message[],\r\n tools: ToolSchema[],\r\n environmentContext?: EnvironmentContext,\r\n mode?: string,\r\n thinkingConfig?: Record<string, any>,\r\n abortSignal?: AbortSignal,\r\n modelName?: string,\r\n modelUid?: string\r\n ): AsyncGenerator<StreamChunk, void, unknown> {\r\n // Check if using a local Ollama model\r\n if (this.isUsingLocalModel()) {\r\n const localModel = this.getCurrentModel();\r\n const supportsTools = OllamaService.modelSupportsTools(localModel);\r\n\r\n quickLog(`[${new Date().toISOString()}] [AIServiceClient] Routing to local Ollama model: ${localModel}, supportsTools: ${supportsTools}\\n`);\r\n\r\n // Show warning only if tools are provided but model doesn't support them\r\n if (tools.length > 0 && !supportsTools) {\r\n yield {\r\n type: 'text',\r\n content: `⚠️ Note: Model \"${localModel}\" does not support tool calling. Running in text-only mode.\\n\\n`,\r\n };\r\n }\r\n\r\n // Route to local Ollama with tools\r\n yield* this.streamLocalChat(localModel, messages, tools);\r\n return;\r\n }\r\n\r\n // Build request payload for cloud backend\r\n const payload: ChatRequest = {\r\n model,\r\n modelUid,\r\n modelName,\r\n messages,\r\n tools,\r\n stream: true,\r\n clientType: 'cli',\r\n environmentContext,\r\n mode,\r\n thinkingConfig,\r\n };\r\n\r\n // Get authentication token from api client\r\n if (!apiClient.isAuthenticated()) {\r\n yield {\r\n type: 'error',\r\n message: 'Authentication required. Please sign in.',\r\n code: 'AUTH_REQUIRED',\r\n };\r\n return;\r\n }\r\n\r\n // Retry logic for transient errors\r\n let lastError: ErrorChunk | null = null;\r\n\r\n for (let attempt = 0; attempt < this.maxRetries; attempt++) {\r\n try {\r\n // Make fetch request to backend AI endpoint\r\n // NOTE: We intentionally do NOT set a timeout here because:\r\n // 1. Tool execution happens during streaming (in-stream execution)\r\n // 2. Interactive commands may require user input for arbitrary duration\r\n // 3. The only timeout should be explicit user cancellation via abortSignal\r\n // Connection issues will manifest as network errors, not timeouts\r\n\r\n const response = await fetch(`${this.getBaseURL()}/chat/completions`, {\r\n method: 'POST',\r\n headers: {\r\n 'Content-Type': 'application/json',\r\n 'Authorization': `Bearer ${this.getSessionToken()}`,\r\n },\r\n body: JSON.stringify(payload),\r\n signal: abortSignal, // Only abort when user explicitly cancels\r\n });\r\n\r\n\r\n\r\n // Check for HTTP errors\r\n if (!response.ok) {\r\n const errorText = await response.text();\r\n const parsedHttpError = this.parseHttpError(response.status, response.statusText, errorText);\r\n\r\n // Handle specific error codes\r\n if (response.status === 401) {\r\n yield {\r\n type: 'error',\r\n message: 'Session expired. Please sign in again.',\r\n code: 'AUTH_REQUIRED',\r\n };\r\n return;\r\n }\r\n\r\n lastError = parsedHttpError;\r\n\r\n if (this.isRetryableError(parsedHttpError.code, parsedHttpError.message, response.status) && attempt < this.maxRetries - 1) {\r\n const retryAfterMs = this.getRetryAfterMs(response.headers);\r\n await this.sleep(this.getRetryDelayMs(attempt, retryAfterMs));\r\n continue;\r\n }\r\n\r\n if (this.isRateLimitError(parsedHttpError.code, parsedHttpError.message, response.status)) {\r\n yield this.createHighDemandError();\r\n return;\r\n }\r\n\r\n yield parsedHttpError;\r\n return;\r\n }\r\n\r\n // Parse SSE stream\r\n // We iterate manually to catch retryable errors from the stream\r\n let contentReceived = false;\r\n let shouldRetry = false;\r\n\r\n for await (const chunk of this.parseSSEStream(response.body)) {\r\n if (chunk.type === 'error') {\r\n const normalizedChunk = this.normalizeErrorChunk(chunk);\r\n\r\n // Check if this is a retryable error (like rate limit) AND we haven't sent partial content yet\r\n if (this.isRetryableError(normalizedChunk.code, normalizedChunk.message) && !contentReceived) {\r\n lastError = normalizedChunk;\r\n shouldRetry = true;\r\n break; // Break the stream loop to trigger retry\r\n }\r\n\r\n // Not retryable or content already sent - yield error and stop\r\n yield normalizedChunk;\r\n return;\r\n }\r\n\r\n // Mark that we've received content\r\n if (chunk.type !== 'done' && chunk.type !== 'thought' && chunk.type !== 'thinking_signature' && chunk.type !== 'file_descriptions') {\r\n contentReceived = true;\r\n }\r\n\r\n yield chunk;\r\n }\r\n\r\n // If stream finished cleanly (without error chunk), we are done\r\n if (!shouldRetry) {\r\n return;\r\n }\r\n\r\n // If we need to retry, check attempts and wait\r\n if (attempt < this.maxRetries - 1) {\r\n // We're going to retry, so we need to wait for the backoff period\r\n await this.sleep(this.getRetryDelayMs(attempt));\r\n continue;\r\n }\r\n\r\n // If we've exhausted retries, we'll fall through to the end of the loop \r\n // and yield the lastError below\r\n\r\n } catch (error: any) {\r\n lastError = this.normalizeThrownError(error);\r\n const wasIntentionalAbort = (error?.name === 'AbortError' || error?.name === 'TimeoutError') && !!abortSignal?.aborted;\r\n\r\n if (wasIntentionalAbort) {\r\n break;\r\n }\r\n\r\n // Retry for transient errors\r\n if (this.isRetryableError(lastError.code, lastError.message) && attempt < this.maxRetries - 1) {\r\n await this.sleep(this.getRetryDelayMs(attempt));\r\n continue;\r\n }\r\n\r\n // If not retryable or max retries reached, yield error and return\r\n break;\r\n }\r\n }\r\n\r\n // If we get here, we've exhausted retries\r\n if (lastError) {\r\n if (this.isRateLimitError(lastError.code, lastError.message)) {\r\n yield this.createHighDemandError();\r\n return;\r\n }\r\n yield lastError;\r\n }\r\n }\r\n\r\n /**\r\n * Create a normalized high-demand error response.\r\n */\r\n private createHighDemandError(): ErrorChunk {\r\n return {\r\n type: 'error',\r\n message: this.highDemandMessage,\r\n code: 'RATE_LIMIT',\r\n };\r\n }\r\n\r\n /**\r\n * Parse backend HTTP error body into a normalized error chunk.\r\n */\r\n private parseHttpError(status: number, statusText: string, errorText: string): ErrorChunk {\r\n let errorMessage = `HTTP ${status}: ${statusText}`;\r\n let errorCode = 'HTTP_ERROR';\r\n\r\n if (errorText) {\r\n try {\r\n const parsed = JSON.parse(errorText);\r\n const rootError = Array.isArray(parsed) ? parsed[0]?.error ?? parsed[0] : parsed?.error ?? parsed;\r\n\r\n if (rootError) {\r\n if (typeof rootError.message === 'string' && rootError.message.trim()) {\r\n errorMessage = rootError.message;\r\n }\r\n if (typeof rootError.code === 'string' || typeof rootError.code === 'number') {\r\n errorCode = String(rootError.code);\r\n } else if (typeof rootError.status === 'string') {\r\n errorCode = rootError.status;\r\n }\r\n }\r\n } catch {\r\n errorMessage = errorText;\r\n }\r\n }\r\n\r\n if (status === 504) {\r\n return {\r\n type: 'error',\r\n message: 'Request timed out. Please try again.',\r\n code: 'TIMEOUT',\r\n };\r\n }\r\n\r\n if (this.isRateLimitError(errorCode, errorMessage, status)) {\r\n return this.createHighDemandError();\r\n }\r\n\r\n return {\r\n type: 'error',\r\n message: errorMessage,\r\n code: errorCode,\r\n };\r\n }\r\n\r\n /**\r\n * Normalize streamed error chunks to stable internal codes/messages.\r\n */\r\n private normalizeErrorChunk(chunk: ErrorChunk): ErrorChunk {\r\n const normalizedCode = chunk.code ? String(chunk.code) : 'UNKNOWN_ERROR';\r\n const normalizedMessage = chunk.message || 'Unknown error occurred';\r\n\r\n if (this.isRateLimitError(normalizedCode, normalizedMessage)) {\r\n return this.createHighDemandError();\r\n }\r\n\r\n if (normalizedCode === '504') {\r\n return {\r\n type: 'error',\r\n message: 'Request timed out. Please try again.',\r\n code: 'TIMEOUT',\r\n };\r\n }\r\n\r\n return {\r\n type: 'error',\r\n message: normalizedMessage,\r\n code: normalizedCode,\r\n };\r\n }\r\n\r\n /**\r\n * Normalize thrown errors from fetch/network/runtime paths.\r\n */\r\n private normalizeThrownError(error: any): ErrorChunk {\r\n const rawMessage = error?.message || 'Unknown error occurred';\r\n const rawCode = error?.code ? String(error.code) : 'UNKNOWN_ERROR';\r\n\r\n if (this.isRateLimitError(rawCode, rawMessage, error?.status)) {\r\n return this.createHighDemandError();\r\n }\r\n\r\n if (error?.name === 'TypeError' && rawMessage.includes('fetch')) {\r\n return {\r\n type: 'error',\r\n message: 'Backend service is unreachable. Please check your connection.',\r\n code: 'NETWORK_ERROR',\r\n };\r\n }\r\n\r\n if (error?.name === 'AbortError' || error?.name === 'TimeoutError') {\r\n return {\r\n type: 'error',\r\n message: 'Request timed out. Please try again.',\r\n code: 'TIMEOUT',\r\n };\r\n }\r\n\r\n return {\r\n type: 'error',\r\n message: rawMessage,\r\n code: rawCode,\r\n };\r\n }\r\n\r\n /**\r\n * Check if an error is a rate-limit/resource-exhausted signal.\r\n */\r\n private isRateLimitError(code?: string, message: string = '', status?: number): boolean {\r\n const normalizedCode = (code || '').toString().toLowerCase();\r\n const normalizedMessage = message.toLowerCase();\r\n\r\n return status === 429 ||\r\n normalizedCode === '429' ||\r\n normalizedCode === '8' ||\r\n normalizedCode === 'rate_limit' ||\r\n normalizedCode === 'resource_exhausted' ||\r\n normalizedMessage.includes('rate limit') ||\r\n normalizedMessage.includes('too many requests') ||\r\n normalizedMessage.includes('quota') ||\r\n normalizedMessage.includes('resource exhausted') ||\r\n normalizedMessage.includes('resource_exhausted') ||\r\n normalizedMessage.includes('maas api returned 429');\r\n }\r\n\r\n /**\r\n * Check if HTTP status is retryable.\r\n */\r\n private isRetryableStatus(status?: number): boolean {\r\n if (typeof status !== 'number') {\r\n return false;\r\n }\r\n return [408, 425, 429, 500, 502, 503, 504].includes(status);\r\n }\r\n\r\n /**\r\n * Check if an error should be retried.\r\n */\r\n private isRetryableError(code: string, message: string = '', status?: number): boolean {\r\n if (this.isRateLimitError(code, message, status)) {\r\n return true;\r\n }\r\n\r\n if (this.isRetryableStatus(status)) {\r\n return true;\r\n }\r\n\r\n const retryableCodes = ['NETWORK_ERROR', 'TIMEOUT', 'UNKNOWN_ERROR', 'RATE_LIMIT'];\r\n if (retryableCodes.includes(code)) {\r\n return true;\r\n }\r\n\r\n const normalizedMessage = message.toLowerCase();\r\n return normalizedMessage.includes('timeout') ||\r\n normalizedMessage.includes('timed out') ||\r\n normalizedMessage.includes('temporarily unavailable') ||\r\n normalizedMessage.includes('network error');\r\n }\r\n\r\n /**\r\n * Parse Retry-After from response headers.\r\n */\r\n private getRetryAfterMs(headers: Headers): number | undefined {\r\n const retryAfterValue = headers.get('retry-after');\r\n if (!retryAfterValue) {\r\n return undefined;\r\n }\r\n\r\n const seconds = Number(retryAfterValue);\r\n if (Number.isFinite(seconds) && seconds >= 0) {\r\n return seconds * 1000;\r\n }\r\n\r\n const retryAfterDate = Date.parse(retryAfterValue);\r\n if (!Number.isNaN(retryAfterDate)) {\r\n return Math.max(0, retryAfterDate - Date.now());\r\n }\r\n\r\n return undefined;\r\n }\r\n\r\n /**\r\n * Compute retry delay with exponential backoff + jitter.\r\n */\r\n private getRetryDelayMs(attempt: number, retryAfterMs?: number): number {\r\n const exponentialDelay = this.retryDelay * Math.pow(2, attempt);\r\n const cappedDelay = Math.min(exponentialDelay, this.maxRetryDelay);\r\n const jitteredDelay = Math.round(Math.min(cappedDelay * (0.5 + Math.random()), this.maxRetryDelay));\r\n\r\n if (retryAfterMs === undefined) {\r\n return jitteredDelay;\r\n }\r\n\r\n const cappedRetryAfter = Math.min(Math.max(0, retryAfterMs), this.maxRetryAfterDelay);\r\n return Math.max(jitteredDelay, cappedRetryAfter);\r\n }\r\n\r\n /**\r\n * Sleep for specified milliseconds\r\n */\r\n private sleep(ms: number): Promise<void> {\r\n return new Promise(resolve => setTimeout(resolve, ms));\r\n }\r\n\r\n /**\r\n * Get session token from apiClient\r\n * This is a workaround since sessionToken is private\r\n */\r\n private getSessionToken(): string {\r\n // Read session token from the same location apiClient uses\r\n const configPath = join(homedir(), '.centaurus', 'session.json');\r\n\r\n try {\r\n if (existsSync(configPath)) {\r\n const data = readFileSync(configPath, 'utf-8');\r\n const session = JSON.parse(data);\r\n return session.sessionToken || '';\r\n }\r\n } catch (error) {\r\n // Return empty string if unable to read\r\n }\r\n\r\n return '';\r\n }\r\n\r\n /**\r\n * Check if the current configuration is using a local Ollama model\r\n */\r\n isUsingLocalModel(): boolean {\r\n const configPath = join(homedir(), '.centaurus', 'config.json');\r\n try {\r\n if (existsSync(configPath)) {\r\n const data = readFileSync(configPath, 'utf-8');\r\n const config = JSON.parse(data);\r\n return config.isLocalModel === true;\r\n }\r\n } catch (error) {\r\n // Return false if unable to read\r\n }\r\n return false;\r\n }\r\n\r\n /**\r\n * Get the current model name from config\r\n */\r\n private getCurrentModel(): string {\r\n const configPath = join(homedir(), '.centaurus', 'config.json');\r\n try {\r\n if (existsSync(configPath)) {\r\n const data = readFileSync(configPath, 'utf-8');\r\n const config = JSON.parse(data);\r\n return config.model || 'gemini-2.5-flash';\r\n }\r\n } catch (error) {\r\n // Return default if unable to read\r\n }\r\n return 'gemini-2.5-flash';\r\n }\r\n\r\n /**\r\n * Convert internal message format to Ollama format (with tool support)\r\n */\r\n private convertToOllamaMessages(messages: Message[]): OllamaChatMessage[] {\r\n return messages.map(msg => {\r\n if (msg.role === 'tool') {\r\n // Tool results - convert to Ollama's tool message format\r\n return {\r\n role: 'tool' as const,\r\n content: msg.content,\r\n tool_name: msg.tool_call_id || 'unknown', // Use tool_call_id as tool_name\r\n };\r\n }\r\n\r\n if (msg.role === 'assistant' && msg.tool_calls?.length) {\r\n // Assistant message with tool calls\r\n return {\r\n role: 'assistant' as const,\r\n content: msg.content || '',\r\n tool_calls: msg.tool_calls.map(tc => ({\r\n function: {\r\n name: tc.name,\r\n arguments: tc.arguments,\r\n }\r\n })),\r\n };\r\n }\r\n\r\n // Regular message\r\n return {\r\n role: msg.role as 'system' | 'user' | 'assistant',\r\n content: msg.content,\r\n };\r\n });\r\n }\r\n\r\n /**\r\n * Convert ToolSchema array to Ollama tool format\r\n */\r\n private convertToolsToOllamaFormat(tools: ToolSchema[]): OllamaTool[] {\r\n return tools.map(tool => ({\r\n type: 'function' as const,\r\n function: {\r\n name: tool.name,\r\n description: tool.description,\r\n parameters: {\r\n type: 'object' as const,\r\n properties: tool.parameters.properties,\r\n required: tool.parameters.required,\r\n },\r\n },\r\n }));\r\n }\r\n\r\n /**\r\n * Stream chat request to local Ollama instance with tool calling support\r\n * \r\n * @param model - The local Ollama model to use (e.g., 'llama3.2:latest')\r\n * @param messages - Conversation history\r\n * @param tools - Available tools (will be converted to Ollama format)\r\n * @yields Stream chunks (text, tool_call, or done events)\r\n */\r\n async *streamLocalChat(\r\n model: string,\r\n messages: Message[],\r\n tools: ToolSchema[] = []\r\n ): AsyncGenerator<StreamChunk, void, unknown> {\r\n try {\r\n const supportsTools = OllamaService.modelSupportsTools(model);\r\n const effectiveTools = supportsTools ? tools : [];\r\n\r\n quickLog(`[${new Date().toISOString()}] [AIServiceClient] Starting local chat with model: ${model}, tools: ${effectiveTools.length}, supportsTools: ${supportsTools}\\n`);\r\n\r\n // Convert messages to Ollama format\r\n const ollamaMessages = this.convertToOllamaMessages(messages);\r\n\r\n // Convert tools to Ollama format if model supports them\r\n const ollamaTools = effectiveTools.length > 0\r\n ? this.convertToolsToOllamaFormat(effectiveTools)\r\n : undefined;\r\n\r\n // Send request to Ollama with tools\r\n const response = await ollamaService.sendChatMessage(model, ollamaMessages, ollamaTools);\r\n\r\n // Check for tool calls in response\r\n if (response.message?.tool_calls?.length) {\r\n quickLog(`[${new Date().toISOString()}] [AIServiceClient] Received ${response.message.tool_calls.length} tool calls from Ollama\\n`);\r\n\r\n // Yield each tool call\r\n for (const toolCall of response.message.tool_calls) {\r\n yield {\r\n type: 'tool_call',\r\n toolCall: {\r\n id: `ollama-${Date.now()}-${toolCall.function.name}`, // Generate unique ID\r\n name: toolCall.function.name,\r\n arguments: toolCall.function.arguments,\r\n },\r\n };\r\n }\r\n\r\n // Signal completion (tool execution will happen in cli-adapter)\r\n yield { type: 'done' };\r\n return;\r\n }\r\n\r\n // No tool calls - yield text response\r\n if (response.message.content) {\r\n yield {\r\n type: 'text',\r\n content: response.message.content,\r\n };\r\n }\r\n\r\n // Signal completion\r\n yield { type: 'done' };\r\n\r\n quickLog(`[${new Date().toISOString()}] [AIServiceClient] Local chat completed\\n`);\r\n } catch (error: any) {\r\n quickLog(`[${new Date().toISOString()}] [AIServiceClient] Local chat error: ${error.message}\\n`);\r\n yield {\r\n type: 'error',\r\n message: error.message || 'Failed to communicate with Ollama',\r\n code: 'OLLAMA_ERROR',\r\n };\r\n }\r\n }\r\n\r\n /**\r\n * Parse Server-Sent Events stream from response body\r\n * \r\n * @param body - ReadableStream from fetch response\r\n * @yields Parsed stream chunks\r\n */\r\n private async *parseSSEStream(\r\n body: ReadableStream<Uint8Array>\r\n ): AsyncGenerator<StreamChunk, void, unknown> {\r\n const reader = body.getReader();\r\n const decoder = new TextDecoder();\r\n let buffer = '';\r\n\r\n try {\r\n while (true) {\r\n const { done, value } = await reader.read();\r\n\r\n if (done) {\r\n // Process any remaining data in buffer before exiting\r\n if (buffer.trim()) {\r\n // Split remaining buffer and process\r\n const remainingLines = buffer.split('\\n');\r\n for (const line of remainingLines) {\r\n if (line.startsWith('data: ')) {\r\n const dataStr = line.slice(6);\r\n if (dataStr.trim()) {\r\n try {\r\n const chunk = JSON.parse(dataStr) as StreamChunk;\r\n yield chunk;\r\n } catch (error) {\r\n // Skip malformed JSON\r\n logWarning(`Failed to parse SSE data (in buffer): ${dataStr}`);\r\n }\r\n }\r\n }\r\n }\r\n }\r\n break;\r\n }\r\n\r\n // Decode chunk and add to buffer\r\n buffer += decoder.decode(value, { stream: true });\r\n\r\n // Process complete lines in buffer\r\n const lines = buffer.split('\\n');\r\n\r\n // Keep the last incomplete line in buffer\r\n buffer = lines.pop() || '';\r\n\r\n for (const line of lines) {\r\n // SSE format: \"data: {json}\"\r\n if (line.startsWith('data: ')) {\r\n const dataStr = line.slice(6); // Remove \"data: \" prefix\r\n\r\n // Skip empty data lines\r\n if (!dataStr.trim()) {\r\n continue;\r\n }\r\n\r\n try {\r\n const chunk = JSON.parse(dataStr) as StreamChunk;\r\n yield chunk;\r\n\r\n // Stop if we receive a done or error event\r\n if (chunk.type === 'done' || chunk.type === 'error') {\r\n return;\r\n }\r\n } catch (error) {\r\n // Skip malformed JSON\r\n logWarning(`Failed to parse SSE data: ${dataStr}`);\r\n }\r\n }\r\n // SSE event type line: \"event: chunk\"\r\n // We don't need to process these separately since the data contains the type\r\n }\r\n }\r\n } finally {\r\n reader.releaseLock();\r\n }\r\n }\r\n}\r\n\r\n// Export singleton instance\r\nexport const aiServiceClient = new AIServiceClient();\r\n"],"mappings":"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","names":[]}