From 12bd80960a1b6b21c8c88b808368ac454ccf7b2e Mon Sep 17 00:00:00 2001 From: OmniNode CI Date: Sat, 20 Sep 2025 14:40:53 +0000 Subject: [PATCH 1/4] fix: resolve TypeScript strict mode errors in providerErrorHandler.ts - Add proper type guards for error object property access - Create ErrorWithStatus and ErrorWithMessage interfaces - Implement hasStatusProperty() and hasMessageProperty() type guards - Replace unsafe object property access with type-safe checks - All 8 TypeScript strict mode errors now resolved - Maintains existing functionality for LLM provider error handling Fixes #686 --- .../.claude-flow/metrics/agent-metrics.json | 1 + .../.claude-flow/metrics/performance.json | 9 +++++++ .../.claude-flow/metrics/task-metrics.json | 10 +++++++ .../knowledge/utils/providerErrorHandler.ts | 27 ++++++++++++++++--- 4 files changed, 44 insertions(+), 3 deletions(-) create mode 100644 archon-ui-main/.claude-flow/metrics/agent-metrics.json create mode 100644 archon-ui-main/.claude-flow/metrics/performance.json create mode 100644 archon-ui-main/.claude-flow/metrics/task-metrics.json diff --git a/archon-ui-main/.claude-flow/metrics/agent-metrics.json b/archon-ui-main/.claude-flow/metrics/agent-metrics.json new file mode 100644 index 0000000000..9e26dfeeb6 --- /dev/null +++ b/archon-ui-main/.claude-flow/metrics/agent-metrics.json @@ -0,0 +1 @@ +{} \ No newline at end of file diff --git a/archon-ui-main/.claude-flow/metrics/performance.json b/archon-ui-main/.claude-flow/metrics/performance.json new file mode 100644 index 0000000000..f16fe7596c --- /dev/null +++ b/archon-ui-main/.claude-flow/metrics/performance.json @@ -0,0 +1,9 @@ +{ + "startTime": 1758379182777, + "totalTasks": 1, + "successfulTasks": 1, + "failedTasks": 0, + "totalAgents": 0, + "activeAgents": 0, + "neuralEvents": 0 +} \ No newline at end of file diff --git a/archon-ui-main/.claude-flow/metrics/task-metrics.json b/archon-ui-main/.claude-flow/metrics/task-metrics.json new file mode 100644 index 0000000000..1b5d860f4e --- /dev/null +++ b/archon-ui-main/.claude-flow/metrics/task-metrics.json @@ -0,0 +1,10 @@ +[ + { + "id": "cmd-hooks-1758379182820", + "type": "hooks", + "success": true, + "duration": 8.685624999999987, + "timestamp": 1758379182829, + "metadata": {} + } +] \ No newline at end of file diff --git a/archon-ui-main/src/features/knowledge/utils/providerErrorHandler.ts b/archon-ui-main/src/features/knowledge/utils/providerErrorHandler.ts index 655a08fd8c..819307b101 100644 --- a/archon-ui-main/src/features/knowledge/utils/providerErrorHandler.ts +++ b/archon-ui-main/src/features/knowledge/utils/providerErrorHandler.ts @@ -10,6 +10,26 @@ export interface ProviderError extends Error { isProviderError?: boolean; } +// Type guards for error object properties +interface ErrorWithStatus { + statusCode?: number; + status?: number; +} + +interface ErrorWithMessage { + message?: string; +} + +// Type guard functions +function hasStatusProperty(obj: unknown): obj is ErrorWithStatus { + return typeof obj === "object" && obj !== null && + ("statusCode" in obj || "status" in obj); +} + +function hasMessageProperty(obj: unknown): obj is ErrorWithMessage { + return typeof obj === "object" && obj !== null && "message" in obj; +} + /** * Parse backend error responses into provider-aware error objects */ @@ -18,12 +38,13 @@ export function parseProviderError(error: unknown): ProviderError { // Check if this is a structured provider error from backend if (error && typeof error === "object") { - if (error.statusCode || error.status) { + // Type-safe status code extraction + if (hasStatusProperty(error)) { providerError.statusCode = error.statusCode || error.status; } - // Parse backend error structure - if (error.message && error.message.includes("detail")) { + // Parse backend error structure with type safety + if (hasMessageProperty(error) && error.message && error.message.includes("detail")) { try { const parsed = JSON.parse(error.message); if (parsed.detail && parsed.detail.error_type) { From 5c2f1be8523fd41d23f7cecedb428cc1a861c585 Mon Sep 17 00:00:00 2001 From: jonahgabriel Date: Sat, 20 Sep 2025 15:15:04 -0400 Subject: [PATCH 2/4] fix: apply biome linting improvements to providerErrorHandler.ts - Use optional chaining instead of logical AND for property access - Improve formatting for better readability - Maintain all existing functionality while addressing linter warnings --- .../src/features/knowledge/utils/providerErrorHandler.ts | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/archon-ui-main/src/features/knowledge/utils/providerErrorHandler.ts b/archon-ui-main/src/features/knowledge/utils/providerErrorHandler.ts index 819307b101..ee1bf806ad 100644 --- a/archon-ui-main/src/features/knowledge/utils/providerErrorHandler.ts +++ b/archon-ui-main/src/features/knowledge/utils/providerErrorHandler.ts @@ -22,8 +22,7 @@ interface ErrorWithMessage { // Type guard functions function hasStatusProperty(obj: unknown): obj is ErrorWithStatus { - return typeof obj === "object" && obj !== null && - ("statusCode" in obj || "status" in obj); + return typeof obj === "object" && obj !== null && ("statusCode" in obj || "status" in obj); } function hasMessageProperty(obj: unknown): obj is ErrorWithMessage { @@ -47,7 +46,7 @@ export function parseProviderError(error: unknown): ProviderError { if (hasMessageProperty(error) && error.message && error.message.includes("detail")) { try { const parsed = JSON.parse(error.message); - if (parsed.detail && parsed.detail.error_type) { + if (parsed.detail?.error_type) { providerError.isProviderError = true; providerError.provider = parsed.detail.provider || "LLM"; providerError.errorType = parsed.detail.error_type; From 00d3d9a22d33d89bf66df7a38eb740a674cc372e Mon Sep 17 00:00:00 2001 From: jonahgabriel Date: Sat, 20 Sep 2025 15:36:59 -0400 Subject: [PATCH 3/4] chore: remove .claude-flow directory - Remove unnecessary .claude-flow metrics files - Clean up repository structure --- archon-ui-main/.claude-flow/metrics/agent-metrics.json | 1 - archon-ui-main/.claude-flow/metrics/performance.json | 9 --------- archon-ui-main/.claude-flow/metrics/task-metrics.json | 10 ---------- 3 files changed, 20 deletions(-) delete mode 100644 archon-ui-main/.claude-flow/metrics/agent-metrics.json delete mode 100644 archon-ui-main/.claude-flow/metrics/performance.json delete mode 100644 archon-ui-main/.claude-flow/metrics/task-metrics.json diff --git a/archon-ui-main/.claude-flow/metrics/agent-metrics.json b/archon-ui-main/.claude-flow/metrics/agent-metrics.json deleted file mode 100644 index 9e26dfeeb6..0000000000 --- a/archon-ui-main/.claude-flow/metrics/agent-metrics.json +++ /dev/null @@ -1 +0,0 @@ -{} \ No newline at end of file diff --git a/archon-ui-main/.claude-flow/metrics/performance.json b/archon-ui-main/.claude-flow/metrics/performance.json deleted file mode 100644 index f16fe7596c..0000000000 --- a/archon-ui-main/.claude-flow/metrics/performance.json +++ /dev/null @@ -1,9 +0,0 @@ -{ - "startTime": 1758379182777, - "totalTasks": 1, - "successfulTasks": 1, - "failedTasks": 0, - "totalAgents": 0, - "activeAgents": 0, - "neuralEvents": 0 -} \ No newline at end of file diff --git a/archon-ui-main/.claude-flow/metrics/task-metrics.json b/archon-ui-main/.claude-flow/metrics/task-metrics.json deleted file mode 100644 index 1b5d860f4e..0000000000 --- a/archon-ui-main/.claude-flow/metrics/task-metrics.json +++ /dev/null @@ -1,10 +0,0 @@ -[ - { - "id": "cmd-hooks-1758379182820", - "type": "hooks", - "success": true, - "duration": 8.685624999999987, - "timestamp": 1758379182829, - "metadata": {} - } -] \ No newline at end of file From aa9a98928b4037e2e6efe7bbfda926a61f0abc8f Mon Sep 17 00:00:00 2001 From: jonahgabriel Date: Sat, 20 Sep 2025 16:31:06 -0400 Subject: [PATCH 4/4] Add comprehensive test coverage for providerErrorHandler TypeScript strict mode fixes - Added 24 comprehensive tests for parseProviderError and getProviderErrorMessage - Tests cover all error scenarios: basic errors, status codes, structured provider errors, malformed JSON, null/undefined handling, and TypeScript strict mode compliance - Fixed null/undefined handling in parseProviderError to properly return fallback messages - All tests passing (24/24) ensuring TypeScript strict mode fixes work correctly - Validates error handling for OpenAI, Google AI, Anthropic, and other LLM providers Related to PR #720 TypeScript strict mode compliance --- .../knowledge/utils/providerErrorHandler.ts | 47 +-- .../utils/tests/providerErrorHandler.test.ts | 281 ++++++++++++++++++ 2 files changed, 310 insertions(+), 18 deletions(-) create mode 100644 archon-ui-main/src/features/knowledge/utils/tests/providerErrorHandler.test.ts diff --git a/archon-ui-main/src/features/knowledge/utils/providerErrorHandler.ts b/archon-ui-main/src/features/knowledge/utils/providerErrorHandler.ts index ee1bf806ad..588d49bcd2 100644 --- a/archon-ui-main/src/features/knowledge/utils/providerErrorHandler.ts +++ b/archon-ui-main/src/features/knowledge/utils/providerErrorHandler.ts @@ -33,28 +33,39 @@ function hasMessageProperty(obj: unknown): obj is ErrorWithMessage { * Parse backend error responses into provider-aware error objects */ export function parseProviderError(error: unknown): ProviderError { - const providerError = error as ProviderError; + // Handle null, undefined, and non-object types + if (!error || typeof error !== "object") { + const result: ProviderError = { + name: "Error", + } as ProviderError; - // Check if this is a structured provider error from backend - if (error && typeof error === "object") { - // Type-safe status code extraction - if (hasStatusProperty(error)) { - providerError.statusCode = error.statusCode || error.status; + // Only set message for non-null/undefined values + if (error) { + result.message = String(error); } - // Parse backend error structure with type safety - if (hasMessageProperty(error) && error.message && error.message.includes("detail")) { - try { - const parsed = JSON.parse(error.message); - if (parsed.detail?.error_type) { - providerError.isProviderError = true; - providerError.provider = parsed.detail.provider || "LLM"; - providerError.errorType = parsed.detail.error_type; - providerError.message = parsed.detail.message || error.message; - } - } catch { - // If parsing fails, use message as-is + return result; + } + + const providerError = error as ProviderError; + + // Type-safe status code extraction + if (hasStatusProperty(error)) { + providerError.statusCode = error.statusCode || error.status; + } + + // Parse backend error structure with type safety + if (hasMessageProperty(error) && error.message && error.message.includes("detail")) { + try { + const parsed = JSON.parse(error.message); + if (parsed.detail?.error_type) { + providerError.isProviderError = true; + providerError.provider = parsed.detail.provider || "LLM"; + providerError.errorType = parsed.detail.error_type; + providerError.message = parsed.detail.message || error.message; } + } catch { + // If parsing fails, use message as-is } } diff --git a/archon-ui-main/src/features/knowledge/utils/tests/providerErrorHandler.test.ts b/archon-ui-main/src/features/knowledge/utils/tests/providerErrorHandler.test.ts new file mode 100644 index 0000000000..193e2444f5 --- /dev/null +++ b/archon-ui-main/src/features/knowledge/utils/tests/providerErrorHandler.test.ts @@ -0,0 +1,281 @@ +import { describe, it, expect } from 'vitest'; +import { parseProviderError, getProviderErrorMessage, type ProviderError } from '../providerErrorHandler'; + +describe('providerErrorHandler', () => { + describe('parseProviderError', () => { + it('should handle basic Error objects', () => { + const error = new Error('Basic error message'); + const result = parseProviderError(error); + + expect(result.message).toBe('Basic error message'); + expect(result.isProviderError).toBeUndefined(); + }); + + it('should handle errors with statusCode property', () => { + const error = { statusCode: 401, message: 'Unauthorized' }; + const result = parseProviderError(error); + + expect(result.statusCode).toBe(401); + expect(result.message).toBe('Unauthorized'); + }); + + it('should handle errors with status property', () => { + const error = { status: 429, message: 'Rate limited' }; + const result = parseProviderError(error); + + expect(result.statusCode).toBe(429); + expect(result.message).toBe('Rate limited'); + }); + + it('should prioritize statusCode over status when both are present', () => { + const error = { statusCode: 401, status: 429, message: 'Auth error' }; + const result = parseProviderError(error); + + expect(result.statusCode).toBe(401); + }); + + it('should parse structured provider errors from backend', () => { + const error = { + message: JSON.stringify({ + detail: { + error_type: 'authentication_failed', + provider: 'OpenAI', + message: 'Invalid API key' + } + }) + }; + + const result = parseProviderError(error); + + expect(result.isProviderError).toBe(true); + expect(result.provider).toBe('OpenAI'); + expect(result.errorType).toBe('authentication_failed'); + expect(result.message).toBe('Invalid API key'); + }); + + it('should handle malformed JSON in message gracefully', () => { + const error = { + message: 'invalid json { detail' + }; + + const result = parseProviderError(error); + + expect(result.isProviderError).toBeUndefined(); + expect(result.message).toBe('invalid json { detail'); + }); + + it('should handle null and undefined inputs safely', () => { + expect(() => parseProviderError(null)).not.toThrow(); + expect(() => parseProviderError(undefined)).not.toThrow(); + + const nullResult = parseProviderError(null); + const undefinedResult = parseProviderError(undefined); + + expect(nullResult).toBeDefined(); + expect(undefinedResult).toBeDefined(); + }); + + it('should handle empty objects', () => { + const result = parseProviderError({}); + + expect(result).toBeDefined(); + expect(result.statusCode).toBeUndefined(); + expect(result.isProviderError).toBeUndefined(); + }); + + it('should handle primitive values', () => { + expect(() => parseProviderError('string error')).not.toThrow(); + expect(() => parseProviderError(42)).not.toThrow(); + expect(() => parseProviderError(true)).not.toThrow(); + }); + + it('should handle structured errors without provider field', () => { + const error = { + message: JSON.stringify({ + detail: { + error_type: 'quota_exhausted', + message: 'Usage limit exceeded' + } + }) + }; + + const result = parseProviderError(error); + + expect(result.isProviderError).toBe(true); + expect(result.provider).toBe('LLM'); // Default fallback + expect(result.errorType).toBe('quota_exhausted'); + expect(result.message).toBe('Usage limit exceeded'); + }); + + it('should handle partial structured errors', () => { + const error = { + message: JSON.stringify({ + detail: { + error_type: 'rate_limit' + // Missing message field + } + }) + }; + + const result = parseProviderError(error); + + expect(result.isProviderError).toBe(true); + expect(result.errorType).toBe('rate_limit'); + expect(result.message).toBe(error.message); // Falls back to original message + }); + }); + + describe('getProviderErrorMessage', () => { + it('should return user-friendly message for authentication_failed', () => { + const error: ProviderError = { + name: 'Error', + message: 'Auth failed', + isProviderError: true, + provider: 'OpenAI', + errorType: 'authentication_failed' + }; + + const result = getProviderErrorMessage(error); + expect(result).toBe('Please verify your OpenAI API key in Settings.'); + }); + + it('should return user-friendly message for quota_exhausted', () => { + const error: ProviderError = { + name: 'Error', + message: 'Quota exceeded', + isProviderError: true, + provider: 'Google AI', + errorType: 'quota_exhausted' + }; + + const result = getProviderErrorMessage(error); + expect(result).toBe('Google AI quota exhausted. Please check your billing settings.'); + }); + + it('should return user-friendly message for rate_limit', () => { + const error: ProviderError = { + name: 'Error', + message: 'Rate limited', + isProviderError: true, + provider: 'Anthropic', + errorType: 'rate_limit' + }; + + const result = getProviderErrorMessage(error); + expect(result).toBe('Anthropic rate limit exceeded. Please wait and try again.'); + }); + + it('should return generic provider message for unknown error types', () => { + const error: ProviderError = { + name: 'Error', + message: 'Unknown error', + isProviderError: true, + provider: 'OpenAI', + errorType: 'unknown_error' + }; + + const result = getProviderErrorMessage(error); + expect(result).toBe('OpenAI API error. Please check your configuration.'); + }); + + it('should use default provider when provider is missing', () => { + const error: ProviderError = { + name: 'Error', + message: 'Auth failed', + isProviderError: true, + errorType: 'authentication_failed' + }; + + const result = getProviderErrorMessage(error); + expect(result).toBe('Please verify your LLM API key in Settings.'); + }); + + it('should handle 401 status code for non-provider errors', () => { + const error = { statusCode: 401, message: 'Unauthorized' }; + + const result = getProviderErrorMessage(error); + expect(result).toBe('Please verify your API key in Settings.'); + }); + + it('should return original message for non-provider errors', () => { + const error = new Error('Network connection failed'); + + const result = getProviderErrorMessage(error); + expect(result).toBe('Network connection failed'); + }); + + it('should return default message when no message is available', () => { + const error = {}; + + const result = getProviderErrorMessage(error); + expect(result).toBe('An error occurred.'); + }); + + it('should handle complex error objects with structured backend response', () => { + const backendError = { + statusCode: 400, + message: JSON.stringify({ + detail: { + error_type: 'authentication_failed', + provider: 'OpenAI', + message: 'API key invalid or expired' + } + }) + }; + + const result = getProviderErrorMessage(backendError); + expect(result).toBe('Please verify your OpenAI API key in Settings.'); + }); + + it('should handle edge case: message contains "detail" but is not JSON', () => { + const error = { + message: 'Error detail: something went wrong' + }; + + const result = getProviderErrorMessage(error); + expect(result).toBe('Error detail: something went wrong'); + }); + + it('should handle null and undefined gracefully', () => { + expect(getProviderErrorMessage(null)).toBe('An error occurred.'); + expect(getProviderErrorMessage(undefined)).toBe('An error occurred.'); + }); + }); + + describe('TypeScript strict mode compliance', () => { + it('should handle type-safe property access', () => { + // Test that our type guards work properly + const errorWithStatus = { statusCode: 500 }; + const errorWithMessage = { message: 'test' }; + const errorWithBoth = { statusCode: 401, message: 'unauthorized' }; + + // These should not throw TypeScript errors and should work correctly + expect(() => parseProviderError(errorWithStatus)).not.toThrow(); + expect(() => parseProviderError(errorWithMessage)).not.toThrow(); + expect(() => parseProviderError(errorWithBoth)).not.toThrow(); + + const result1 = parseProviderError(errorWithStatus); + const result2 = parseProviderError(errorWithMessage); + const result3 = parseProviderError(errorWithBoth); + + expect(result1.statusCode).toBe(500); + expect(result2.message).toBe('test'); + expect(result3.statusCode).toBe(401); + expect(result3.message).toBe('unauthorized'); + }); + + it('should handle objects without expected properties safely', () => { + const objectWithoutStatus = { someOtherProperty: 'value' }; + const objectWithoutMessage = { anotherProperty: 42 }; + + expect(() => parseProviderError(objectWithoutStatus)).not.toThrow(); + expect(() => parseProviderError(objectWithoutMessage)).not.toThrow(); + + const result1 = parseProviderError(objectWithoutStatus); + const result2 = parseProviderError(objectWithoutMessage); + + expect(result1.statusCode).toBeUndefined(); + expect(result2.message).toBeUndefined(); + }); + }); +}); \ No newline at end of file