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
134 changes: 134 additions & 0 deletions .aiox-core/core/errors/aiox-error.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,134 @@
const {
DEFAULT_ERROR_CODE,
} = require('./constants');
const { defaultErrorRegistry } = require('./error-registry');
const { serializeError } = require('./serializer');
const { deepMerge, hasOwn, isPlainObject, normalizeErrorCode } = require('./utils');

class AIOXError extends Error {
constructor(message, options = {}) {
const code = normalizeErrorCode(options.code) || DEFAULT_ERROR_CODE;
const registry = options.registry || defaultErrorRegistry;
const definition = registry.lookup(code);
const finalMessage = message || options.message || definition.userMessage || code;

if (hasOwn(options, 'cause')) {
super(finalMessage, { cause: options.cause });
} else {
super(finalMessage);
}

this.name = 'AIOXError';
this.code = code;
this.category = options.category || definition.category;
this.severity = options.severity || definition.severity;
this.retryable = hasOwn(options, 'retryable') ? Boolean(options.retryable) : Boolean(definition.retryable);
this.userMessage = options.userMessage || definition.userMessage;
this.recovery = Array.isArray(options.recovery) ? [...options.recovery] : [...(definition.recovery || [])];
this.metadata = deepMerge(definition.metadata || {}, options.metadata || {});
this.isAIOXError = true;

if (hasOwn(options, 'exitCode')) {
this.exitCode = options.exitCode;
} else if (hasOwn(definition, 'exitCode')) {
this.exitCode = definition.exitCode;
}

if (hasOwn(options, 'cause')) {
this.cause = options.cause;
}

if (Error.captureStackTrace) {
Error.captureStackTrace(this, AIOXError);
}
}

toJSON(options = {}) {
return serializeError(this, options);
}
}

function isAIOXError(value) {
return value instanceof AIOXError || Boolean(value && value.isAIOXError === true);
}

function collectErrorOwnProperties(error) {
if (!(error instanceof Error)) {
return {};
}

return Object.getOwnPropertyNames(error).reduce((properties, key) => {
if (['name', 'message', 'stack', 'cause'].includes(key)) {
return properties;
}

properties[key] = error[key];
return properties;
}, {});
}

function normalizeError(error, overrides = {}) {
if (isAIOXError(error)) {
if (!overrides || Object.keys(overrides).length === 0) {
return error;
}

return new AIOXError(overrides.message || error.message, {
code: overrides.code || error.code,
category: overrides.category || error.category,
severity: overrides.severity || error.severity,
retryable: hasOwn(overrides, 'retryable') ? overrides.retryable : error.retryable,
exitCode: hasOwn(overrides, 'exitCode') ? overrides.exitCode : error.exitCode,
userMessage: overrides.userMessage || error.userMessage,
recovery: overrides.recovery || error.recovery,
metadata: deepMerge(error.metadata || {}, overrides.metadata || {}),
cause: hasOwn(overrides, 'cause') ? overrides.cause : error.cause,
registry: overrides.registry,
});
}

if (error instanceof Error) {
const ownProperties = collectErrorOwnProperties(error);
const metadata = deepMerge(
{
originalError: {
name: error.name || 'Error',
},
},
Object.keys(ownProperties).length > 0 ? { originalError: { properties: ownProperties } } : {},
isPlainObject(overrides.metadata) ? overrides.metadata : {},
);

return new AIOXError(overrides.message || error.message, {
code: overrides.code || error.code || DEFAULT_ERROR_CODE,
category: overrides.category,
severity: overrides.severity,
retryable: overrides.retryable,
exitCode: overrides.exitCode,
userMessage: overrides.userMessage,
recovery: overrides.recovery,
metadata,
cause: hasOwn(overrides, 'cause') ? overrides.cause : error,
registry: overrides.registry,
});
}

return new AIOXError(overrides.message || String(error), {
code: overrides.code || DEFAULT_ERROR_CODE,
category: overrides.category,
severity: overrides.severity,
retryable: overrides.retryable,
exitCode: overrides.exitCode,
userMessage: overrides.userMessage,
recovery: overrides.recovery,
metadata: deepMerge({ originalValue: { type: typeof error } }, overrides.metadata || {}),
cause: overrides.cause,
registry: overrides.registry,
});
}

module.exports = {
AIOXError,
isAIOXError,
normalizeError,
};
137 changes: 137 additions & 0 deletions .aiox-core/core/errors/constants.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,137 @@
const ErrorCategory = Object.freeze({
CONFIGURATION: 'configuration',
VALIDATION: 'validation',
FILESYSTEM: 'filesystem',
NETWORK: 'network',
REGISTRY: 'registry',
ORCHESTRATION: 'orchestration',
SYNAPSE: 'synapse',
EXECUTION: 'execution',
PERMISSION: 'permission',
EXTERNAL_EXECUTOR: 'external_executor',
UNKNOWN: 'unknown',
});

const ErrorSeverity = Object.freeze({
CRITICAL: 'critical',
ERROR: 'error',
WARNING: 'warning',
INFO: 'info',
});

const DEFAULT_ERROR_CODE = 'AIOX_UNKNOWN_ERROR';

const CORE_ERROR_DEFINITIONS = Object.freeze([
{
code: DEFAULT_ERROR_CODE,
category: ErrorCategory.UNKNOWN,
severity: ErrorSeverity.ERROR,
retryable: false,
userMessage: 'An unexpected AIOX core error occurred.',
recovery: ['Review the error metadata and retry if the operation is safe to repeat.'],
},
{
code: 'AIOX_CONFIGURATION_INVALID',
category: ErrorCategory.CONFIGURATION,
severity: ErrorSeverity.ERROR,
retryable: false,
userMessage: 'AIOX configuration is invalid.',
recovery: ['Validate the active AIOX configuration and retry.'],
},
{
code: 'AIOX_VALIDATION_FAILED',
category: ErrorCategory.VALIDATION,
severity: ErrorSeverity.ERROR,
retryable: false,
userMessage: 'AIOX validation failed.',
recovery: ['Review validation errors and correct the invalid input.'],
},
{
code: 'AIOX_FILESYSTEM_ERROR',
category: ErrorCategory.FILESYSTEM,
severity: ErrorSeverity.ERROR,
retryable: true,
userMessage: 'A filesystem operation failed.',
recovery: ['Check path existence, permissions, and disk availability.'],
},
{
code: 'AIOX_PERMISSION_DENIED',
category: ErrorCategory.PERMISSION,
severity: ErrorSeverity.ERROR,
retryable: false,
exitCode: 13,
userMessage: 'The operation does not have the required permissions.',
recovery: ['Grant the required permission or run the command in an authorized context.'],
},
{
code: 'AIOX_NETWORK_ERROR',
category: ErrorCategory.NETWORK,
severity: ErrorSeverity.ERROR,
retryable: true,
userMessage: 'A network operation failed.',
recovery: ['Check connectivity and retry the operation.'],
},
{
code: 'AIOX_REGISTRY_LOAD_FAILED',
category: ErrorCategory.REGISTRY,
severity: ErrorSeverity.ERROR,
retryable: false,
userMessage: 'AIOX could not load a registry.',
recovery: ['Validate registry file syntax and path configuration.'],
},
{
code: 'AIOX_REGISTRY_WRITE_FAILED',
category: ErrorCategory.REGISTRY,
severity: ErrorSeverity.ERROR,
retryable: true,
userMessage: 'AIOX could not write a registry.',
recovery: ['Check registry path permissions and retry.'],
},
{
code: 'AIOX_ORCHESTRATION_FAILED',
category: ErrorCategory.ORCHESTRATION,
severity: ErrorSeverity.ERROR,
retryable: false,
userMessage: 'AIOX orchestration failed.',
recovery: ['Review orchestration metadata and the active workflow state.'],
},
{
code: 'AIOX_SYNAPSE_LAYER_FAILED',
category: ErrorCategory.SYNAPSE,
severity: ErrorSeverity.WARNING,
retryable: true,
userMessage: 'A Synapse layer failed while processing context.',
recovery: ['Review layer metadata and continue with graceful degradation when possible.'],
},
{
code: 'AIOX_EXECUTION_FAILED',
category: ErrorCategory.EXECUTION,
severity: ErrorSeverity.ERROR,
retryable: false,
userMessage: 'AIOX execution failed.',
recovery: ['Review execution logs and retry after correcting the failure cause.'],
},
{
code: 'AIOX_EXTERNAL_EXECUTOR_FAILED',
category: ErrorCategory.EXTERNAL_EXECUTOR,
severity: ErrorSeverity.ERROR,
retryable: true,
userMessage: 'An external executor failed.',
recovery: ['Review external executor logs and retry if the command is idempotent.'],
},
{
code: 'AIOX_PERSISTENCE_DEGRADED',
category: ErrorCategory.FILESYSTEM,
severity: ErrorSeverity.WARNING,
retryable: true,
userMessage: 'AIOX persistence degraded and continued in memory.',
recovery: ['Check persistence path permissions and available disk space.'],
},
]);

module.exports = {
ErrorCategory,
ErrorSeverity,
DEFAULT_ERROR_CODE,
CORE_ERROR_DEFINITIONS,
};
Loading
Loading