/**
 * Vercel AI SDK Compatibility Layer
 *
 * Provides compatibility with the Vercel AI SDK by implementing the
 * LanguageModelV1 interface. This allows NeuroLink to be used as a
 * drop-in replacement for other AI providers in AI SDK applications.
 *
 * @module @neurolink/ai-sdk
 */
import { logger } from "../utils/logger.js";
import { createClient, NeuroLinkClient } from "./httpClient.js";
// =============================================================================
// ClientLanguageModel Implementation
// =============================================================================
/**
 * NeuroLink Language Model implementation compatible with Vercel AI SDK
 *
 * Implements the LanguageModelV1 interface for drop-in compatibility.
 *
 * @example Using with AI SDK
 * ```typescript
 * import { generateText } from "ai-sdk";
 * // Replace "ai-sdk" with the Vercel AI SDK package name in your project.
 * import { createNeuroLinkModel } from '@neurolink/ai-sdk';
 *
 * const model = createNeuroLinkModel({
 *   baseUrl: 'https://api.neurolink.example.com',
 *   apiKey: 'your-api-key',
 * });
 *
 * const result = await generateText({
 *   model: model('gpt-4o'),
 *   prompt: 'Hello, world!',
 * });
 * ```
 */
export class NeuroLinkLanguageModel {
    modelId;
    provider;
    client;
    options;
    constructor(client, modelId, provider, options = {}) {
        this.client = client;
        this.modelId = modelId;
        this.provider = provider;
        this.options = options;
    }
    /**
     * Generate a non-streaming response
     */
    async doGenerate(options) {
        const { prompt, system, messages, temperature, maxTokens, abortSignal } = options;
        // Build the input text from prompt or messages
        let inputText = prompt ?? "";
        if (messages && messages.length > 0) {
            inputText = messages
                .filter((m) => m.role !== "system")
                .map((m) => m.content)
                .join("\n");
        }
        // Extract system message if present
        const systemPrompt = system ?? messages?.find((m) => m.role === "system")?.content;
        try {
            const response = await this.client.generate({
                input: { text: inputText },
                provider: this.provider,
                model: this.modelId,
                temperature: temperature ?? this.options.temperature,
                maxTokens: maxTokens ?? this.options.maxTokens,
                systemPrompt,
            }, {
                signal: abortSignal,
            });
            return {
                text: response.data.content,
                finishReason: this.mapFinishReason(response.data.finishReason),
                usage: {
                    promptTokens: response.data.usage?.promptTokens ?? 0,
                    completionTokens: response.data.usage?.completionTokens ?? 0,
                },
                rawResponse: response,
            };
        }
        catch (error) {
            return {
                text: "",
                finishReason: "error",
                usage: { promptTokens: 0, completionTokens: 0 },
                rawResponse: error,
            };
        }
    }
    /**
     * Generate a streaming response
     *
     * Uses an async queue so that each text delta from the provider is yielded
     * to the consumer immediately, rather than buffering the entire response.
     */
    async doStream(options) {
        const { prompt, system, messages, temperature, maxTokens, abortSignal } = options;
        // Build the input text from prompt or messages
        let inputText = prompt ?? "";
        if (messages && messages.length > 0) {
            inputText = messages
                .filter((m) => m.role !== "system")
                .map((m) => m.content)
                .join("\n");
        }
        // Extract system message if present
        const systemPrompt = system ?? messages?.find((m) => m.role === "system")?.content;
        // ---- Async queue (push/pull pattern) ----
        const buffer = [];
        let finished = false;
        let notifyConsumer = null;
        /** Resolves the consumer's pending pull, if any. */
        function wake() {
            if (notifyConsumer) {
                const fn = notifyConsumer;
                notifyConsumer = null;
                fn();
            }
        }
        /** Push a chunk into the queue and wake the consumer. */
        function push(chunk) {
            buffer.push(chunk);
            wake();
        }
        /** Signal that no more chunks will arrive. */
        function close() {
            finished = true;
            wake();
        }
        // Kick off the underlying stream in the background.
        // Errors are forwarded to the consumer via the queue.
        const self = this;
        const streamPromise = self.client
            .stream({
            input: { text: inputText },
            provider: self.provider,
            model: self.modelId,
            temperature: temperature ?? self.options.temperature,
            maxTokens: maxTokens ?? self.options.maxTokens,
            systemPrompt,
        }, {
            onText: (text) => {
                push({ type: "text-delta", textDelta: text });
            },
            onDone: (result) => {
                push({
                    type: "finish",
                    finishReason: self.mapFinishReason(result.finishReason),
                    usage: result.usage
                        ? {
                            promptTokens: result.usage.promptTokens,
                            completionTokens: result.usage.completionTokens,
                        }
                        : undefined,
                });
                close();
            },
        }, {
            signal: abortSignal,
        })
            .catch(() => {
            // If the stream rejects before onDone fires, emit a finish/error.
            if (!finished) {
                push({ type: "finish", finishReason: "error" });
                close();
            }
        });
        // Suppress unhandled-rejection warnings; the consumer drains the queue.
        // Errors are pushed into the queue as finish events with error details.
        streamPromise.catch((err) => {
            const message = err instanceof Error ? err.message : String(err);
            push({
                type: "finish",
                finishReason: "error",
                usage: { promptTokens: 0, completionTokens: 0 },
            });
            close();
            logger.debug("[aiSdkAdapter] Stream promise rejected", {
                error: message,
            });
        });
        // ---- Async iterable that pulls from the queue ----
        async function* createStream() {
            while (true) {
                // Drain anything already buffered.
                while (buffer.length > 0) {
                    const chunk = buffer.shift();
                    if (!chunk) {
                        break;
                    }
                    yield chunk;
                    if (chunk.type === "finish") {
                        return;
                    }
                }
                // Nothing buffered — if the producer is done, exit.
                if (finished) {
                    return;
                }
                // Wait for the producer to push more data.
                await new Promise((resolve) => {
                    notifyConsumer = resolve;
                });
            }
        }
        return {
            stream: createStream(),
        };
    }
    /**
     * Map NeuroLink finish reasons to AI SDK finish reasons
     */
    mapFinishReason(reason) {
        if (!reason) {
            return "stop";
        }
        const mapping = {
            stop: "stop",
            length: "length",
            "tool-calls": "tool-calls",
            tool_calls: "tool-calls",
            "content-filter": "content-filter",
            content_filter: "content-filter",
            error: "error",
        };
        return mapping[reason] ?? "other";
    }
}
// =============================================================================
// Provider Factory
// =============================================================================
/**
 * NeuroLink Provider for Vercel AI SDK
 *
 * Creates model instances that are compatible with the Vercel AI SDK.
 *
 * @example
 * ```typescript
 * import { neurolink } from '@neurolink/ai-sdk';
 *
 * const provider = neurolink({
 *   baseUrl: 'https://api.neurolink.example.com',
 *   apiKey: 'your-api-key',
 * });
 *
 * // Create a model
 * const model = provider('gpt-4o');
 *
 * // Use with AI SDK
 * const result = await generateText({
 *   model,
 *   prompt: 'Hello!',
 * });
 * ```
 */
export class NeuroLinkProvider {
    client;
    defaultProvider;
    defaultModel;
    constructor(options) {
        this.client = createClient({
            baseUrl: options.baseUrl,
            apiKey: options.apiKey,
            token: options.token,
            headers: options.headers,
        });
        this.defaultProvider = options.defaultProvider ?? "openai";
        this.defaultModel = options.defaultModel ?? "gpt-4o";
    }
    /**
     * Create a language model instance
     *
     * @param modelId - Model ID (e.g., 'gpt-4o', 'claude-3-opus')
     * @param options - Additional model options
     */
    model(modelId, options) {
        const model = modelId ?? this.defaultModel;
        const provider = options?.provider ?? this.inferProvider(model);
        return new NeuroLinkLanguageModel(this.client, model, provider, options);
    }
    /**
     * Alias for model() - makes the provider callable
     */
    call(modelId, options) {
        return this.model(modelId, options);
    }
    /**
     * Infer provider from model ID
     */
    inferProvider(modelId) {
        const providerPatterns = {
            openai: [/^gpt-/, /^o1-/, /^o3-/, /^davinci/, /^curie/],
            anthropic: [/^claude-/],
            "google-ai": [/^gemini-/, /^palm-/],
            vertex: [/^gemini-/, /^palm-/, /^codechat-/],
            mistral: [/^mistral-/, /^codestral-/],
            bedrock: [/^anthropic\./, /^amazon\./],
            azure: [/^azure-/],
        };
        for (const [provider, patterns] of Object.entries(providerPatterns)) {
            for (const pattern of patterns) {
                if (pattern.test(modelId)) {
                    return provider;
                }
            }
        }
        return this.defaultProvider;
    }
    /**
     * Get the underlying client
     */
    getClient() {
        return this.client;
    }
}
// =============================================================================
// Factory Functions
// =============================================================================
/**
 * Create a NeuroLink provider for Vercel AI SDK
 *
 * @example
 * ```typescript
 * import { createNeuroLinkProvider, generateText } from "@neurolink/ai-sdk";
 *
 * const neurolink = createNeuroLinkProvider({
 *   baseUrl: 'https://api.neurolink.example.com',
 *   apiKey: process.env.NEUROLINK_API_KEY,
 * });
 *
 * const result = await generateText({
 *   model: neurolink('gpt-4o'),
 *   prompt: 'Hello!',
 * });
 * ```
 */
export function createNeuroLinkProvider(options) {
    const provider = new NeuroLinkProvider(options);
    // Make the provider callable
    const callable = (modelId, modelOptions) => provider.model(modelId, modelOptions);
    // Copy all methods from provider to callable
    Object.setPrototypeOf(callable, provider);
    Object.assign(callable, {
        model: provider.model.bind(provider),
        call: provider.call.bind(provider),
        getClient: provider.getClient.bind(provider),
    });
    return callable;
}
/**
 * Create a standalone NeuroLink model for Vercel AI SDK
 *
 * @example
 * ```typescript
 * import { createNeuroLinkModel, generateText } from "@neurolink/ai-sdk";
 *
 * const model = createNeuroLinkModel({
 *   baseUrl: 'https://api.neurolink.example.com',
 *   apiKey: process.env.NEUROLINK_API_KEY,
 *   modelId: 'gpt-4o',
 *   provider: 'openai',
 * });
 *
 * const result = await generateText({
 *   model,
 *   prompt: 'Hello!',
 * });
 * ```
 */
export function createNeuroLinkModel(options) {
    const provider = createNeuroLinkProvider(options);
    return provider(options.modelId, { provider: options.provider });
}
// =============================================================================
// Convenience Exports
// =============================================================================
/**
 * Default export for easy provider creation
 *
 * @example
 * ```typescript
 * import { neurolink } from '@neurolink/ai-sdk';
 *
 * const provider = neurolink({
 *   baseUrl: 'https://api.neurolink.example.com',
 *   apiKey: 'your-key',
 * });
 *
 * const model = provider('gpt-4o');
 * ```
 */
export const neurolink = createNeuroLinkProvider;
// =============================================================================
// Streaming Utilities
// =============================================================================
/**
 * Create an AI SDK compatible streaming response from NeuroLink stream
 *
 * @example
 * ```typescript
 * // In a Next.js API route or server action
 * import { createStreamingResponse } from '@neurolink/ai-sdk';
 *
 * export async function POST(req: Request) {
 *   const { prompt } = await req.json();
 *
 *   const stream = await createStreamingResponse({
 *     baseUrl: process.env.NEUROLINK_URL,
 *     apiKey: process.env.NEUROLINK_API_KEY,
 *     input: { text: prompt },
 *     provider: 'openai',
 *     model: 'gpt-4o',
 *   });
 *
 *   return stream;
 * }
 * ```
 */
export async function createStreamingResponse(options) {
    const client = createClient({
        baseUrl: options.baseUrl,
        apiKey: options.apiKey,
        token: options.token,
        headers: options.headers,
    });
    // Create a ReadableStream for the response
    const encoder = new TextEncoder();
    const stream = new ReadableStream({
        async start(controller) {
            try {
                await client.stream({
                    input: options.input,
                    provider: options.provider ?? "openai",
                    model: options.model,
                    temperature: options.temperature,
                    maxTokens: options.maxTokens,
                    systemPrompt: options.systemPrompt,
                }, {
                    onText: (text) => {
                        // AI SDK format: stream text deltas
                        const data = JSON.stringify({
                            type: "text-delta",
                            textDelta: text,
                        });
                        controller.enqueue(encoder.encode(`data: ${data}\n\n`));
                    },
                    onToolCall: (toolCall) => {
                        const data = JSON.stringify({ type: "tool-call", toolCall });
                        controller.enqueue(encoder.encode(`data: ${data}\n\n`));
                    },
                    onToolResult: (toolResult) => {
                        const data = JSON.stringify({ type: "tool-result", toolResult });
                        controller.enqueue(encoder.encode(`data: ${data}\n\n`));
                    },
                    onDone: (result) => {
                        const data = JSON.stringify({
                            type: "finish",
                            finishReason: result.finishReason ?? "stop",
                            usage: result.usage,
                        });
                        controller.enqueue(encoder.encode(`data: ${data}\n\n`));
                        controller.enqueue(encoder.encode("data: [DONE]\n\n"));
                        controller.close();
                    },
                    onError: (error) => {
                        const data = JSON.stringify({ type: "error", error });
                        controller.enqueue(encoder.encode(`data: ${data}\n\n`));
                        controller.close();
                    },
                });
            }
            catch (error) {
                const data = JSON.stringify({
                    type: "error",
                    error: { message: error.message },
                });
                controller.enqueue(encoder.encode(`data: ${data}\n\n`));
                controller.close();
            }
        },
    });
    return new Response(stream, {
        headers: {
            "Content-Type": "text/event-stream",
            "Cache-Control": "no-cache",
            Connection: "keep-alive",
        },
    });
}
// =============================================================================
// Type Re-exports
// =============================================================================
// Re-export types from ./types.js for convenience
// Note: NeuroLinkProviderOptions and ClientModelOptions are defined and exported above
//# sourceMappingURL=aiSdkAdapter.js.map