/**
 * Behavior AI Service — generates pure TypeScript function bodies for
 * behavior steps that weren't matched by the realize engine's convention
 * patterns.
 *
 * Architecture (post AI-SDK replatform 2026-04; delivery unified 2026-05):
 * - Delegates to the Vercel AI SDK via the model resolver. Provider choice
 *   is driven by SPECVERSE_AI_PROVIDER (claude-cli | anthropic |
 *   openai-compatible | stub) with sensible defaults per environment.
 * - Claude-CLI provider preserves the Max-subscription zero-cost path
 *   (spawn claude --print --session-id/--resume). API provider uses
 *   Anthropic prompt caching for equivalent savings.
 * - The behavior prompt loads via the shared `loadPrompt('behavior')` (composed
 *   bundle preferred, partials inlined; runtime-expanded source as fallback) and
 *   the system message is built by the shared `assembleDeliverySystem`: thin
 *   (role + read-the-skill pointer) on claude-cli when the `behavior` family
 *   skill is installed — the CLI auto-loads the grounding — else the full
 *   composed grounding inlined. Same delivery path runPrompt uses, so behavior
 *   gets the family-skill token savings for free.
 *
 * Used by ai-behaviors-generator at realize time and by `spv ai regenerate`.
 */
import { generateText } from 'ai';
import { resolveModel, resolveProviderId } from './model-resolver.js';
import { loadPrompt, assembleDeliverySystem } from './prompt-runner.js';
const DEFAULT_ROLE = 'You are a SpecVerse behavior code generator. Produce pure TypeScript function bodies.';
const DEFAULT_MINIMAL_CONTEXT = 'Output ONLY the function body. No imports, no database access, no event publishing. Return a plain value. Throw Error("...") on failure.';
/**
 * Service class kept for API compatibility with ai-behaviors-generator.
 * Internally delegates to the AI SDK via the model resolver.
 */
export class BehaviorAIService {
    model;
    providerId;
    userTemplate;
    system;
    timeout;
    /** Non-null after generateBehavior() succeeds once. */
    get isAvailable() {
        return this.providerId !== 'stub';
    }
    constructor(options = {}) {
        this.model = resolveModel({ model: options.model });
        this.providerId = resolveProviderId();
        this.timeout = options.timeout ?? 120_000;
        // Load the canonical behavior prompt through the shared loader: it prefers
        // the COMPOSED bundle (partials inlined at build time) and runtime-expands
        // the source as a fallback — so `system.{role,minimalContext,fullContext}`
        // hold real grounding, never literal `{{> partial}}` tokens. (Reading the
        // raw source directly, as this service used to, broke once those fields were
        // factored into partials.) Falls back to built-in defaults if assets aren't
        // resolvable.
        let prompt = null;
        try {
            prompt = loadPrompt('behavior');
        }
        catch {
            prompt = null;
        }
        this.userTemplate = prompt?.user?.template?.trim() || DEFAULT_USER_TEMPLATE;
        // One delivery decision, shared with runPrompt: thin (role + read-the-skill
        // pointer) on claude-cli when the `behavior` family skill is installed — the
        // CLI auto-loads the grounding; otherwise the full composed grounding inlined.
        this.system = prompt
            ? assembleDeliverySystem(prompt, this.providerId, 'behavior')
            : `${DEFAULT_ROLE}\n\n${DEFAULT_MINIMAL_CONTEXT}`;
    }
    /**
     * Start a new session for a controller. Session semantics are now owned
     * by the provider — for claude-cli that's the spawn-with-session-id
     * mechanism; for anthropic API it's the prompt-cache marker. We just
     * record a no-op for callers that expect a session id to log.
     */
    startSession(controllerName) {
        return `${controllerName}-${Date.now()}`;
    }
    /** Generate a function body for a behavior step. */
    async generateBehavior(ctx) {
        if (!this.isAvailable)
            return null;
        const userPrompt = this.buildUserPrompt(ctx);
        try {
            const result = await generateText({
                model: this.model,
                system: this.system,
                prompt: userPrompt,
                abortSignal: AbortSignal.timeout(this.timeout),
                // Anthropic prompt caching: mark the system message as cacheable
                // so subsequent calls within a cache window get the 90% discount.
                // Other providers ignore this.
                providerOptions: {
                    anthropic: {
                        cacheControl: { type: 'ephemeral' },
                    },
                },
            });
            return this.extractCode(result.text);
        }
        catch {
            return null;
        }
    }
    /** End the current session — no-op now that session state lives in the provider. */
    endSession() {
        /* nothing to do — provider manages its own lifecycle */
    }
    buildUserPrompt(ctx) {
        const template = this.userTemplate;
        const inputVars = ctx.parameterNames.length > 0 ? ctx.parameterNames.join(', ') : 'none';
        const inputSignature = ctx.parameterNames.length > 0
            ? `{ ${ctx.parameterNames.map((n) => `${n}: any`).join('; ')} }`
            : 'Record<string, never>';
        const returnType = ctx.returnType || 'any';
        return template
            .replace(/\{\{step\}\}/g, ctx.step)
            .replace(/\{\{modelName\}\}/g, ctx.modelName)
            .replace(/\{\{operationName\}\}/g, ctx.operationName)
            .replace(/\{\{functionName\}\}/g, ctx.functionName)
            .replace(/\{\{parameterNames\}\}/g, inputVars)
            .replace(/\{\{inputVars\}\}/g, inputVars)
            .replace(/\{\{inputSignature\}\}/g, inputSignature)
            .replace(/\{\{returnType\}\}/g, returnType)
            .replace(/\{\{availableModels\}\}/g, ctx.availableModels.join(', ') || 'none');
    }
    extractCode(output) {
        let code = output.trim();
        const codeBlock = code.match(/```(?:typescript|ts|javascript|js)?\n([\s\S]*?)```/);
        if (codeBlock)
            code = codeBlock[1].trim();
        const funcMatch = code.match(/^(?:export\s+)?(?:async\s+)?function\s+\w+\([^)]*\)[^{]*\{([\s\S]*)\}\s*$/);
        if (funcMatch)
            code = funcMatch[1].trim();
        return code || null;
    }
}
const DEFAULT_USER_TEMPLATE = `Generate a PURE TypeScript function body for this SpecVerse behavior step.

Step: "{{step}}"

Function: async function {{functionName}}(input: {{inputSignature}}): Promise<{{returnType}}>

Available: {{inputVars}}
Available Prisma models (types only): {{availableModels}}

Output ONLY the function body (between { and }). No markdown fences.`;
//# sourceMappingURL=behavior-ai-service.js.map