// source/utils/type-helpers.ts
/**
 * Type-safe helper utilities for handling non-string content
 *
 * This module provides type guards and conversion functions that:
 * 1. Accept unknown types (string, object, array, null, undefined)
 * 2. Convert to appropriate types for processing
 * 3. Preserve types in memory (critical for ToolCall.arguments)
 * 4. Provide safe fallbacks for edge cases
 *
 * TYPE PRESERVATION STRATEGY
 * ===========================
 *
 * "Preserving types" means:
 * 1. When receiving LLM responses (which can be ANY type):
 *    - We accept unknown types (string, object, array, null, undefined)
 *    - We convert to string ONLY for PARSING OPERATIONS
 *    - We preserve the original type in the tool call structure
 *
 * 2. When storing ToolCall.arguments:
 *    - MUST preserve as Record<string, unknown> (object type)
 *    - NOT convert to string
 *    - Enables direct property access without JSON.parse
 *
 * 3. When displaying/writing to disk:
 *    - Convert to string for display/storage operations
 *    - Use JSON.stringify for objects/arrays
 *    - Use String() for primitives
 *
 * The confusion comes from mixing up:
 * - "Preserve types in memory" (CRITICAL: ToolCall.arguments stays as object)
 * - "Convert to string for processing" (NECESSARY: Parser expects strings)
 */
// ============================================================================
// TYPE GUARDS
// ============================================================================
/**
 * Type guard to check if value is a string
 *
 * @param value - Value to check
 * @returns True if value is a string, false otherwise
 */
export function isString(value) {
    return typeof value === 'string';
}
/**
 * Type guard to check if value is a non-null object
 *
 * @param value - Value to check
 * @returns True if value is a non-null object, false otherwise
 */
export function isObject(value) {
    return typeof value === 'object' && value !== null && !Array.isArray(value);
}
/**
 * Type guard to check if value is an array
 *
 * @param value - Value to check
 * @returns True if value is an array, false otherwise
 */
export function isArray(value) {
    return Array.isArray(value);
}
/**
 * Type guard to check if value is a plain object (not null, not array, not instance)
 *
 * @param value - Value to check
 * @returns True if value is a plain object, false otherwise
 */
export function isPlainObject(value) {
    if (value === null || typeof value !== 'object') {
        return false;
    }
    // Check for prototype chain
    const prototype = Object.getPrototypeOf(value);
    return prototype === null || prototype === Object.prototype;
}
/**
 * Type guard to check if value is a valid function
 *
 * @param value - Value to check
 * @returns True if value is a function, false otherwise
 */
export function isFunction(value) {
    return typeof value === 'function';
}
/**
 * Type guard to check if value is a valid number
 *
 * @param value - Value to check
 * @returns True if value is a number, false otherwise
 */
export function isNumber(value) {
    return typeof value === 'number' && !isNaN(value) && isFinite(value);
}
/**
 * Type guard to check if value is a valid boolean
 *
 * @param value - Value to check
 * @returns True if value is a boolean, false otherwise
 */
export function isBoolean(value) {
    return typeof value === 'boolean';
}
/**
 * Type guard to check if value is a valid null value
 *
 * @param value - Value to check
 * @returns True if value is null, false otherwise
 */
export function isNull(value) {
    return value === null;
}
/**
 * Type guard to check if value is undefined
 *
 * @param value - Value to check
 * @returns True if value is undefined, false otherwise
 */
export function isUndefined(value) {
    return value === undefined;
}
// ============================================================================
// STRING CONVERSION FUNCTIONS
// ============================================================================
/**
 * Ensures value is a string for display/storage operations
 *
 * This function is used for:
 * 1. DISPLAY OPERATIONS - where we need to display content
 * 2. STORAGE OPERATIONS - where we write to disk
 *
 * For display/storage, we convert to string.
 *
 * @param value - Value to convert (unknown type)
 * @returns String representation of the value
 *
 * @example
 * ```typescript
 * // ToolCall.arguments is an object in memory
 * const toolCall = {
 *   function: {
 *     name: 'write_file',
 *     arguments: {path: "/tmp/test.txt", content: "hello"}
 *   }
 * };
 *
 * // For storage, convert to string
 * const contentStr = ensureString(toolCall.function.arguments);
 * // contentStr = '{"path": "/tmp/test.txt", "content": "hello"}'
 *
 * await writeFile(path, contentStr, 'utf-8');
 * ```
 */
export function ensureString(value) {
    // Handle null/undefined
    if (value === null || value === undefined) {
        return '';
    }
    // Handle string - return as-is
    if (isString(value)) {
        return value;
    }
    // Handle number - convert to string
    if (isNumber(value)) {
        return String(value);
    }
    // Handle boolean - convert to string
    if (isBoolean(value)) {
        return String(value);
    }
    // Handle array - convert to JSON string
    if (isArray(value)) {
        return JSON.stringify(value);
    }
    // Handle object - convert to JSON string
    if (isObject(value)) {
        return JSON.stringify(value);
    }
    // Fallback for unknown types
    return String(value);
}
/**
 * Coerce a single selectable "option" into clean display text.
 *
 * Models sometimes emit option lists as objects (e.g. `{label, value}` or
 * `{description}`) despite a schema asking for plain strings. Pull the
 * meaningful text out of common shapes so both the displayed option and any
 * answer echoed back to the model are clean strings, not JSON blobs or opaque
 * machine ids.
 *
 * Human-readable keys are preferred over `value`: in the standard
 * `{label, value}` select shape, `value` is usually an id (e.g.
 * "quicklinks_only") and `label` is what the user should see.
 *
 * Shared by the `ask_user` tool and the ACP question handler so both paths
 * coerce identically.
 */
export function toOptionString(option) {
    if (isString(option))
        return option;
    if (isObject(option)) {
        for (const key of [
            'label',
            'text',
            // MiniMax M3 wraps option text in a `$text` key.
            '$text',
            'title',
            'name',
            'description',
            'content',
            'value',
        ]) {
            const candidate = option[key];
            if (typeof candidate === 'string' && candidate.trim() !== '') {
                return candidate;
            }
        }
        // Some models emit each option as a single-entry map where the KEY is
        // the display text and the value is empty, e.g.
        // `{"Playfair Display - elegant, high-contrast, editorial": ""}`. None
        // of the known keys match, so fall back to the lone key as the label.
        // Guarded on an empty value so genuinely-keyed objects like `{foo: 1}`
        // still serialize to JSON.
        const entries = Object.entries(option);
        if (entries.length === 1) {
            const [key, value] = entries[0];
            if (key.trim() !== '' &&
                (value === '' || value === null || value === undefined)) {
                return key;
            }
        }
    }
    return ensureString(option);
}
// ============================================================================
// UTILITY FUNCTIONS
// ============================================================================
/**
 * Checks if value is empty
 *
 * @param value - Value to check
 * @returns True if value is empty, false otherwise
 */
export function isEmpty(value) {
    // Check for null or undefined
    if (value === null || value === undefined) {
        return true;
    }
    // Check for empty string
    if (isString(value) && value === '') {
        return true;
    }
    // Check for empty array
    if (isArray(value) && value.length === 0) {
        return true;
    }
    // Check for empty object
    if (isObject(value) && Object.keys(value).length === 0) {
        return true;
    }
    return false;
}
/**
 * Checks if value is non-empty (opposite of isEmpty)
 *
 * @param value - Value to check
 * @returns True if value is non-empty, false otherwise
 */
export function isNotEmpty(value) {
    return !isEmpty(value);
}
//# sourceMappingURL=type-helpers.js.map