Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 6 additions & 6 deletions memory-bank/activeContext.md
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
# Active Context

## 🧠 **CURRENT STATUS: AUTOMATIC CONTEXT SUMMARIZATION IMPLEMENTED** (2025-08-08)
## 🛡️ **CURRENT STATUS: TYPE-SAFE ERROR HANDLING REFACTORING COMPLETE** (2025-08-20)

### **✅ SDK-Style Context Management Feature Complete**
- **Primary Objective**: ✅ Implement automatic, stateful context summarization as a non-breaking feature.
- **Major Discovery**: The feature was successfully integrated using a new `enableContextSummarization()` method on the `NeuroLink` class, preserving state across calls.
- **Current Phase**: ✅ COMPLETE SUCCESS - The feature is implemented and verified with a live end-to-end test.
- **Status**: 🎉 **FEATURE COMPLETE** - Ready for documentation and release.
### **✅ Robust Application-Wide Error Handling Achieved**
- **Primary Objective**: ✅ Replace the fragile, string-based error detection system with a modern, type-safe architecture.
- **Major Discovery**: Implementing a custom error hierarchy (`AuthenticationError`, `NetworkError`, etc.) allows for reliable error identification using `instanceof`, making the system more stable and easier to maintain.
- **Current Phase**: ✅ COMPLETE SUCCESS - The new system is implemented, documented in the memory bank, and all changes have been committed.
- **Status**: 🎉 **FEATURE COMPLETE** - The application is now more resilient and provides a better user experience.

### **🚀 CLI ENHANCEMENT: VERSION FLAG**
- **Feature**: Added `--version` flag to the CLI.
Expand Down
16 changes: 16 additions & 0 deletions memory-bank/progress.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,21 @@
# Project Progress

## 🛡️ **TYPE-SAFE ERROR HANDLING REFACTORING COMPLETE** (2025-08-20)

### **🏆 LATEST ACHIEVEMENT: ROBUST APPLICATION-WIDE ERROR HANDLING**

**Objective**: Replace the fragile, string-based error detection system with a modern, type-safe architecture to improve stability and maintainability.
**Achievement**: Successfully implemented a new system using custom error classes. This ensures consistent error handling across all providers and delivers clearer, more actionable feedback to the user.
**Impact**: The application is now more resilient to changes in external API error messages, easier to debug, and provides a significantly better user experience.

**Technical Breakthrough**:
- ✅ **Custom Error Hierarchy**: Introduced a new `src/lib/types/errors.ts` file with specific classes like `AuthenticationError`, `NetworkError`, and `RateLimitError`.
- ✅ **Provider Responsibility**: Refactored all AI providers to throw these new, specific error types instead of generic `Error` objects.
- ✅ **Intelligent CLI Handling**: Updated the CLI's `handleError` function to use `instanceof` checks, allowing it to catch specific error types and provide targeted, helpful advice.
- ✅ **Improved Maintainability**: The new system is cleaner, more readable, and easier to extend with new error types in the future.

---

## 🎉 **EVENTEMITTER INTEGRATION COMPLETE** (2025-01-08)

### **🏆 LATEST ACHIEVEMENT: REAL-TIME EVENT MONITORING SYSTEM**
Expand Down
51 changes: 51 additions & 0 deletions memory-bank/systemPatterns.md
Original file line number Diff line number Diff line change
Expand Up @@ -766,6 +766,57 @@ The best provider is selected based on the following priorities:

## Error Handling Patterns

### Type-Safe Provider Error Handling (August 20, 2025)

**Pattern**: A centralized, type-safe error system replaces fragile string-matching to create a more robust and maintainable application. This pattern ensures that errors are handled consistently and that users receive clear, actionable feedback.

**1. Custom Error Hierarchy (`src/lib/types/errors.ts`)**
A set of custom error classes provides a specific vocabulary for application failures.

```typescript
// src/lib/types/errors.ts
export class BaseError extends Error { /* ... */ }
export class ProviderError extends BaseError { /* ... */ }
export class AuthenticationError extends ProviderError { /* ... */ }
export class NetworkError extends ProviderError { /* ... */ }
export class RateLimitError extends ProviderError { /* ... */ }
export class InvalidModelError extends ProviderError { /* ... */ }
```

**2. Providers Throw Specific Errors**
Each AI provider is responsible for interpreting low-level errors and throwing the appropriate high-level, typed error.

```typescript
// Example in a provider (e.g., openAI.ts)
protected handleProviderError(error: unknown): Error {
const message = (error as Error).message;
if (message.includes("Invalid API key")) {
throw new AuthenticationError("Invalid OpenAI API key.", this.providerName);
}
// ... other checks
throw new ProviderError(`OpenAI error: ${message}`, this.providerName);
}
```

**3. CLI Catches Typed Errors for User Feedback**
The CLI's `handleError` function uses `instanceof` to catch specific error types and provide targeted advice.

```typescript
// Example in the CLI (src/cli/index.ts)
function handleError(error: Error, context: string): void {
logger.error(`❌ ${context} failed: ${error.message}`);

if (error instanceof AuthenticationError) {
logger.error("💡 Please check your API key and environment variables.");
} else if (error instanceof NetworkError) {
logger.error("💡 Please check your internet connection.");
}
process.exit(1);
}
```

---

1. **Provider-Level Error Handling**:

- Each provider handles provider-specific errors
Expand Down
98 changes: 16 additions & 82 deletions src/cli/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,12 @@ import { fileURLToPath } from "url";
import { addOllamaCommands } from "./commands/ollama.js";
import { addSageMakerCommands } from "./commands/sagemaker.js";
import { CLICommandFactory } from "./factories/commandFactory.js";

import {
AuthenticationError,
AuthorizationError,
NetworkError,
RateLimitError,
} from "../lib/types/errors.js";
import { logger } from "../lib/utils/logger.js";

// Get version from package.json
Expand All @@ -45,79 +50,9 @@ try {
// Utility Functions (Simple, Zero Maintenance)

function handleError(error: Error, context: string): void {
const specificErrorMessage = error.message;
const originalErrorMessageLowerCase = error.message
? error.message.toLowerCase()
: "";
const errorStringLowerCase = String(error).toLowerCase();

let isAuthError = false;
let genericMessage = specificErrorMessage; // Initialize genericMessage with the specific one

if (
originalErrorMessageLowerCase.includes("api_key") ||
originalErrorMessageLowerCase.includes("google_ai_api_key") ||
originalErrorMessageLowerCase.includes("aws_access_key_id") ||
originalErrorMessageLowerCase.includes("aws_secret_access_key") ||
originalErrorMessageLowerCase.includes("aws_session_token") ||
originalErrorMessageLowerCase.includes("google_application_credentials") ||
originalErrorMessageLowerCase.includes("google_service_account_key") ||
originalErrorMessageLowerCase.includes("google_auth_client_email") ||
originalErrorMessageLowerCase.includes("anthropic_api_key") ||
originalErrorMessageLowerCase.includes("azure_openai_api_key")
) {
isAuthError = true;
} else if (
// Fallback to checking the full stringified error if direct message didn't match
errorStringLowerCase.includes("api_key") ||
errorStringLowerCase.includes("google_ai_api_key") ||
errorStringLowerCase.includes("aws_access_key_id") ||
errorStringLowerCase.includes("aws_secret_access_key") ||
errorStringLowerCase.includes("aws_session_token") ||
errorStringLowerCase.includes("google_application_credentials") ||
errorStringLowerCase.includes("google_service_account_key") ||
errorStringLowerCase.includes("google_auth_client_email") ||
errorStringLowerCase.includes("anthropic_api_key") ||
errorStringLowerCase.includes("azure_openai_api_key")
) {
isAuthError = true;
}

if (isAuthError) {
genericMessage =
"Authentication error: Missing or invalid API key/credentials for the selected provider.";
} else if (
originalErrorMessageLowerCase.includes("enotfound") || // Prefer direct message checks
originalErrorMessageLowerCase.includes("econnrefused") ||
originalErrorMessageLowerCase.includes("invalid-endpoint") ||
originalErrorMessageLowerCase.includes("network error") ||
originalErrorMessageLowerCase.includes("could not connect") ||
originalErrorMessageLowerCase.includes("timeout") ||
errorStringLowerCase.includes("enotfound") || // Fallback to full string
errorStringLowerCase.includes("econnrefused") ||
errorStringLowerCase.includes("invalid-endpoint") ||
errorStringLowerCase.includes("network error") ||
errorStringLowerCase.includes("could not connect") ||
errorStringLowerCase.includes("timeout") // General timeout
) {
genericMessage =
"Network error: Could not connect to the API endpoint or the request timed out.";
} else if (
errorStringLowerCase.includes("not authorized") ||
errorStringLowerCase.includes("permission denied")
) {
genericMessage =
"Authorization error: You are not authorized to perform this action or access this resource.";
}
// If no specific condition matched, genericMessage remains error.message
logger.error(chalk.red(`❌ ${context} failed: ${error.message}`));

logger.error(chalk.red(`❌ ${context} failed: ${genericMessage}`));

// Smart hints for common errors (just string matching!)
if (
genericMessage.toLowerCase().includes("api key") ||
genericMessage.toLowerCase().includes("credential")
) {
if (error instanceof AuthenticationError) {
logger.error(
chalk.yellow(
"💡 Set Google AI Studio API key (RECOMMENDED): export GOOGLE_AI_API_KEY=AIza-...",
Expand Down Expand Up @@ -146,18 +81,11 @@ function handleError(error: Error, context: string): void {
"💡 Or set Azure OpenAI credentials: export AZURE_OPENAI_API_KEY=... AZURE_OPENAI_ENDPOINT=...",
),
);
}

if (error.message.toLowerCase().includes("rate limit")) {
} else if (error instanceof RateLimitError) {
logger.error(
chalk.yellow("💡 Try again in a few moments or use --provider vertex"),
);
}

if (
error.message.toLowerCase().includes("not authorized") ||
error.message.toLowerCase().includes("permission denied")
) {
} else if (error instanceof AuthorizationError) {
logger.error(
chalk.yellow(
"💡 Check your account permissions for the selected model/service.",
Expand All @@ -168,6 +96,12 @@ function handleError(error: Error, context: string): void {
"💡 For AWS Bedrock, ensure you have permissions for the specific model and consider using inference profile ARNs.",
),
);
} else if (error instanceof NetworkError) {
logger.error(
chalk.yellow(
"💡 Check your internet connection and the provider's status page.",
),
);
}

process.exit(1);
Expand Down
74 changes: 35 additions & 39 deletions src/lib/providers/anthropic.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,12 @@ import {
TimeoutError,
getDefaultTimeout,
} from "../utils/timeout.js";
import {
AuthenticationError,
NetworkError,
ProviderError,
RateLimitError,
} from "../types/errors.js";
import { DEFAULT_MAX_TOKENS, DEFAULT_MAX_STEPS } from "../core/constants.js";
import {
validateApiKey,
Expand Down Expand Up @@ -83,70 +89,60 @@ export class AnthropicProvider extends BaseProvider {

protected handleProviderError(error: unknown): Error {
if (error instanceof TimeoutError) {
return new Error(
`Anthropic request timed out after ${error.timeout}ms: ${error.message}`,
throw new NetworkError(
`Request timed out after ${error.timeout}ms`,
this.providerName,
);
}

const errorRecord = error as UnknownRecord;
const message =
typeof errorRecord?.message === "string"
? errorRecord.message
: "Unknown error";

// Handle API key errors
if (
(typeof errorRecord?.message === "string" &&
errorRecord.message.includes("API_KEY_INVALID")) ||
(typeof errorRecord?.message === "string" &&
errorRecord.message.includes("Invalid API key"))
message.includes("API_KEY_INVALID") ||
message.includes("Invalid API key")
) {
return new Error(
throw new AuthenticationError(
"Invalid Anthropic API key. Please check your ANTHROPIC_API_KEY environment variable.",
this.providerName,
);
}

// Handle rate limiting errors
if (
typeof errorRecord?.message === "string" &&
(errorRecord.message.includes("rate limit") ||
errorRecord.message.includes("too_many_requests") ||
errorRecord.message.includes("429"))
message.includes("rate limit") ||
message.includes("too_many_requests") ||
message.includes("429")
) {
return new Error(
throw new RateLimitError(
"Anthropic rate limit exceeded. Please try again later.",
this.providerName,
);
}

// Handle connection errors
if (
typeof errorRecord?.message === "string" &&
(errorRecord.message.includes("ECONNRESET") ||
errorRecord.message.includes("ENOTFOUND") ||
errorRecord.message.includes("ECONNREFUSED") ||
errorRecord.message.includes("network") ||
errorRecord.message.includes("connection"))
message.includes("ECONNRESET") ||
message.includes("ENOTFOUND") ||
message.includes("ECONNREFUSED") ||
message.includes("network") ||
message.includes("connection")
) {
return new Error(
"Anthropic API connection error. Please check your internet connection and try again.",
);
throw new NetworkError(`Connection error: ${message}`, this.providerName);
}

// Handle server errors
if (
typeof errorRecord?.message === "string" &&
(errorRecord.message.includes("500") ||
errorRecord.message.includes("502") ||
errorRecord.message.includes("503") ||
errorRecord.message.includes("504") ||
errorRecord.message.includes("server error"))
message.includes("500") ||
message.includes("502") ||
message.includes("503") ||
message.includes("504") ||
message.includes("server error")
) {
return new Error(
"Anthropic API server error. Please try again in a few moments.",
);
throw new ProviderError(`Server error: ${message}`, this.providerName);
}

const message =
typeof errorRecord?.message === "string"
? errorRecord.message
: "Unknown error";
return new Error(`Anthropic error: ${message}`);
throw new ProviderError(`Anthropic error: ${message}`, this.providerName);
}

// executeGenerate removed - BaseProvider handles all generation with tools
Expand Down
Loading