/**
 * Crewly Agent Runtime Type Definitions
 *
 * Types for the in-process Crewly Agent runtime powered by Vercel AI SDK.
 * Covers model configuration, conversation state, and agent lifecycle.
 *
 * @module services/agent/crewly-agent/types
 */
/**
 * All valid model providers
 */
export const MODEL_PROVIDERS = [
    'anthropic',
    'openai',
    'google',
    'ollama',
    'deepseek',
];
/**
 * Tools that perform write/modify operations.
 * Used by readOnlyMode to determine which tools to block.
 */
export const WRITE_TOOLS = [
    'edit_file',
    'write_file',
    'start_agent',
    'stop_agent',
    'handle_agent_failure',
    'delegate_task',
    'send_message',
    'reply_slack',
    'broadcast',
    'schedule_check',
    'cancel_schedule',
    'register_self',
    'report_status',
    'remember',
    'complete_task',
];
/**
 * Estimated context window sizes (in tokens) for known models.
 * Used for budget tracking when the model doesn't report its own limit.
 */
export const MODEL_CONTEXT_WINDOWS = {
    // Anthropic
    'claude-opus-4-20250514': 200_000,
    'claude-sonnet-4-20250514': 200_000,
    'claude-haiku-4-20250506': 200_000,
    // Google
    'gemini-2.0-flash': 1_000_000,
    'gemini-2.5-flash-preview-05-20': 1_000_000,
    'gemini-3-flash-preview': 1_000_000,
    // OpenAI
    'gpt-4o': 128_000,
    'gpt-4o-mini': 128_000,
    'gpt-4-turbo': 128_000,
    // DeepSeek — V3 chat and R1 reasoner both ship with a 64k context window.
    // Without these entries the manager falls back to `default: 128_000`, which
    // would let Crewly's compaction trigger (0.8 × budget) skip past the real
    // 64k cap and let DeepSeek 4xx with context_length_exceeded.
    'deepseek-chat': 64_000,
    'deepseek-reasoner': 64_000,
    // Fallback
    default: 128_000,
};
/**
 * Per-model floor on `maxOutputTokens` (the AI SDK's name for the
 * `max_tokens` request param). Used to defend against a class of bugs where
 * a low user-configured limit silently produces an empty response.
 *
 * Currently only `deepseek-reasoner` has a real floor: R1 mixes
 * `reasoning_tokens` into the same `max_tokens` budget as `completion_tokens`,
 * so a user value < ~200 can be entirely consumed by the reasoning trace,
 * leaving `message.content = ""` (200 OK, no error).
 *
 * Models not listed here resolve to a floor of 0 (no clamp).
 *
 * Discovered 2026-05-03 via live smoke test D — see
 * `.crewly/specs/2026-05-03-crewly-agent-deepseek-gap-list.md` (finding N5).
 */
export const MODEL_OUTPUT_TOKEN_FLOORS = {
    'deepseek-reasoner': 1024,
};
/**
 * Resolve the effective `maxOutputTokens` for a given model configuration,
 * applying any per-model floor from `MODEL_OUTPUT_TOKEN_FLOORS`.
 *
 * If `config.maxTokens` is unset, falls back to the runtime default
 * (`CREWLY_AGENT_DEFAULTS.DEFAULT_MODEL.maxTokens`). The result is always
 * `>= floor` for the model, where `floor` defaults to 0.
 *
 * @param config - Model configuration (may have `maxTokens` undefined)
 * @returns Effective max output tokens to pass to generateText/streamText
 */
export function resolveMaxOutputTokens(config) {
    const requested = config.maxTokens ?? CREWLY_AGENT_DEFAULTS.DEFAULT_MODEL.maxTokens ?? 0;
    const floor = MODEL_OUTPUT_TOKEN_FLOORS[config.modelId] ?? 0;
    return Math.max(requested, floor);
}
/**
 * Default configuration values for Crewly Agent
 */
export const CREWLY_AGENT_DEFAULTS = {
    /** Default max reasoning steps per generateText call (high to mimic unlimited like Claude Code) */
    MAX_STEPS: 500,
    /** Maximum tool calls allowed per single response to prevent polling dead-loops */
    MAX_TOOL_CALLS_PER_RESPONSE: 15,
    /** Consecutive identical tool calls before aborting (loop detection) */
    LOOP_DETECTION_THRESHOLD: 3,
    /** Consecutive error responses (404, 4xx, 5xx) from the same tool before aborting */
    ERROR_LOOP_THRESHOLD: 3,
    /** Default API base URL */
    API_BASE_URL: 'http://localhost:8787',
    /** Default max history messages before compaction */
    MAX_HISTORY_MESSAGES: 100,
    /** Default compaction threshold (80% of context window) */
    COMPACTION_THRESHOLD: 0.8,
    /** Default model configuration */
    DEFAULT_MODEL: {
        provider: 'google',
        modelId: 'gemini-3-flash-preview',
        temperature: 0.3,
        maxTokens: 8192,
    },
    /** HTTP request timeout in milliseconds */
    API_TIMEOUT_MS: 30_000,
    /** Heartbeat interval in milliseconds for keeping in-process agent active between messages */
    HEARTBEAT_INTERVAL_MS: 30_000,
    /** Maximum time in milliseconds for a single message processing (generateText call) — hard abort.
     *  Override via CREWLY_AGENT_MESSAGE_TIMEOUT_MS env var.
     *
     *  This is the **default** timeout used when the model has no entry in
     *  `MODEL_TIMEOUT_MS`. Per-model overrides take precedence — see below. */
    MESSAGE_TIMEOUT_MS: Number(process.env.CREWLY_AGENT_MESSAGE_TIMEOUT_MS) || 300_000,
    /** I4 — Per-model hard-timeout overrides. Looked up by `modelId`.
     *
     *  When the runtime service is about to run a message, it checks
     *  `MODEL_TIMEOUT_MS[config.model.modelId]` first; falls back to
     *  `MESSAGE_TIMEOUT_MS` if no entry exists.
     *
     *  **Why per-model:** different models have radically different latency
     *  characteristics. DeepSeek-R1 (`deepseek-reasoner`) emits chain-of-thought
     *  reasoning_tokens which are slower to generate than plain content tokens.
     *  Live smoke (2026-05-03) measured R1 single-step at avg 2.8s/max 4.8s, but
     *  long-context (>32k) latency rises sharply per vendor docs, and multi-step
     *  agentic loops can accumulate 30+ steps. 5min default is tight; 10min gives
     *  safety margin without inviting runaway loops. See gap-list spec §I4 for
     *  measurement evidence. */
    /**
     * Frozen at module load via `Object.freeze` — defensive guard against
     * runtime mutation. `as const` is compile-time only and the
     * `Record<string, number>` cast widens it back to mutable, so freeze
     * is the only line of defense against accidental writes like
     * `CREWLY_AGENT_DEFAULTS.MODEL_TIMEOUT_MS['x'] = 999` from elsewhere
     * in the codebase. Per-model overrides should be added by editing this
     * file (treat as a config table, not runtime state).
     */
    MODEL_TIMEOUT_MS: Object.freeze({
        /** DeepSeek-R1: 2× default — reasoning chain-of-thought is slower per token. */
        'deepseek-reasoner': 600_000,
    }),
    /** Soft warning threshold in milliseconds — logs a warning but does not kill the request.
     *  Override via CREWLY_AGENT_MESSAGE_SOFT_WARNING_MS env var. */
    MESSAGE_SOFT_WARNING_MS: Number(process.env.CREWLY_AGENT_MESSAGE_SOFT_WARNING_MS) || 240_000,
    /** Cooldown period in milliseconds after rate limit retries are exhausted */
    RATE_LIMIT_COOLDOWN_MS: 300_000,
    /** Maximum number of automatic retries for recoverable errors (429, 5xx, network) */
    MAX_RETRIES: 3,
    /** Base delay in milliseconds for exponential backoff between retries */
    RETRY_BASE_DELAY_MS: 1_000,
    /** Default Ollama API base URL for local LLM provider */
    OLLAMA_BASE_URL: 'http://localhost:11434/api',
    /** Default security policy */
    SECURITY_POLICY: {
        auditEnabled: true,
        requireApproval: [],
        blockedTools: [],
        maxAuditEntries: 500,
        readOnlyMode: false,
    },
    /** Default security guardrail configuration */
    SECURITY_GUARDRAILS: {
        outputFilterEnabled: true,
        envIsolationEnabled: true,
        promptGuardEnabled: true,
        explicitEnvVars: [],
    },
};
/**
 * Type guard to check if a string is a valid ModelProvider
 *
 * @param value - String to check
 * @returns True if the value is a valid ModelProvider
 */
export function isModelProvider(value) {
    return MODEL_PROVIDERS.includes(value);
}
/**
 * Supported models for the UI dropdown.
 * Format: provider/modelId → display label
 */
export const SUPPORTED_MODELS = [
    { id: 'google/gemini-3-flash-preview', label: 'Gemini 3 Flash (Preview)', provider: 'google' },
    { id: 'google/gemini-2.5-flash-preview-05-20', label: 'Gemini 2.5 Flash', provider: 'google' },
    { id: 'anthropic/claude-sonnet-4-20250514', label: 'Claude Sonnet 4', provider: 'anthropic' },
    { id: 'anthropic/claude-haiku-4-20250514', label: 'Claude Haiku 4', provider: 'anthropic' },
    { id: 'openai/gpt-4o', label: 'GPT-4o', provider: 'openai' },
    { id: 'openai/gpt-4o-mini', label: 'GPT-4o Mini', provider: 'openai' },
    { id: 'deepseek/deepseek-chat', label: 'DeepSeek V3', provider: 'deepseek' },
    { id: 'deepseek/deepseek-reasoner', label: 'DeepSeek R1', provider: 'deepseek' },
    { id: 'ollama/llama3', label: 'Llama 3 (Ollama)', provider: 'ollama' },
];
/**
 * Parse a modelId string (provider/modelId) into a ModelConfig.
 * Falls back to DEFAULT_MODEL if the input is invalid.
 *
 * @param modelId - Format: "provider/modelId" (e.g. "google/gemini-3-flash-preview")
 * @returns Parsed ModelConfig
 */
export function parseModelId(modelId) {
    if (!modelId || !modelId.includes('/')) {
        return CREWLY_AGENT_DEFAULTS.DEFAULT_MODEL;
    }
    const slashIdx = modelId.indexOf('/');
    const provider = modelId.substring(0, slashIdx);
    const model = modelId.substring(slashIdx + 1);
    if (!isModelProvider(provider) || !model) {
        return CREWLY_AGENT_DEFAULTS.DEFAULT_MODEL;
    }
    return {
        provider,
        modelId: model,
        temperature: CREWLY_AGENT_DEFAULTS.DEFAULT_MODEL.temperature,
        maxTokens: CREWLY_AGENT_DEFAULTS.DEFAULT_MODEL.maxTokens,
    };
}
/**
 * Type guard to check if an object is a valid ModelConfig
 *
 * @param obj - Object to validate
 * @returns True if the object has all required ModelConfig fields
 */
export function isModelConfig(obj) {
    if (typeof obj !== 'object' || obj === null)
        return false;
    const candidate = obj;
    return (typeof candidate.provider === 'string' &&
        isModelProvider(candidate.provider) &&
        typeof candidate.modelId === 'string' &&
        candidate.modelId.length > 0);
}
//# sourceMappingURL=types.js.map