"use strict";
var __importDefault = (this && this.__importDefault) || function (mod) {
    return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.VertexAiAgentProvider = void 0;
exports.extractReasoningEngineResponseText = extractReasoningEngineResponseText;
const google_auth_library_1 = require("google-auth-library");
const cache_1 = require("../../cache");
const logger_1 = __importDefault(require("../../logger"));
const shared_1 = require("../shared");
const CLOUD_PLATFORM_SCOPE = 'https://www.googleapis.com/auth/cloud-platform';
function parseServiceAccountCredentials(raw) {
    const trimmed = raw.trim();
    if (!trimmed) {
        throw new Error('Vertex AI Agent: service account key is empty');
    }
    let parsed;
    try {
        parsed = JSON.parse(trimmed);
    }
    catch {
        throw new Error('Vertex AI Agent: service account key must be valid JSON');
    }
    if (!parsed || typeof parsed !== 'object' || Array.isArray(parsed)) {
        throw new Error('Vertex AI Agent: service account key must be a JSON object');
    }
    const o = parsed;
    if (o.type !== 'service_account') {
        throw new Error('Vertex AI Agent: key file must be a service account JSON (type: service_account)');
    }
    if (typeof o.private_key !== 'string' || typeof o.client_email !== 'string') {
        throw new Error('Vertex AI Agent: service account JSON must include client_email and private_key');
    }
    return o;
}
function reasoningEngineBaseUrl(config) {
    const projectId = config.projectId?.trim();
    const location = config.location?.trim();
    const engineId = config.reasoningEngineId?.trim();
    if (!projectId || !location || !engineId) {
        throw new Error('Vertex AI Agent: projectId, location, and reasoningEngineId are all required');
    }
    return `https://${location}-aiplatform.googleapis.com/v1/projects/${projectId}/locations/${location}/reasoningEngines/${engineId}`;
}
/** Append `alt=sse` for Vertex streamQuery (required for SSE response bodies). */
function withAltSse(url) {
    const trimmed = url.trim();
    if (/[?&]alt=sse(?:&|$)/i.test(trimmed)) {
        return trimmed;
    }
    return trimmed.includes('?') ? `${trimmed}&alt=sse` : `${trimmed}?alt=sse`;
}
function buildStreamQueryUrl(config) {
    if (config.queryUrl?.trim()) {
        return withAltSse(config.queryUrl.trim());
    }
    return withAltSse(`${reasoningEngineBaseUrl(config)}:streamQuery`);
}
function buildSyncQueryUrl(config) {
    if (config.queryUrl?.trim()) {
        const legacy = config.queryUrl.trim().split('?')[0] ?? '';
        if (legacy.includes(':streamQuery')) {
            return legacy.replace(':streamQuery', ':query');
        }
        if (legacy.includes(':query')) {
            return legacy;
        }
    }
    return `${reasoningEngineBaseUrl(config)}:query`;
}
function buildCreateSessionBody(userId) {
    return {
        class_method: 'async_create_session',
        input: { user_id: userId },
    };
}
function buildStreamQueryBody(userId, sessionId, message) {
    return {
        class_method: 'async_stream_query',
        input: {
            user_id: userId,
            session_id: sessionId,
            message,
        },
    };
}
function sessionRefFromApiName(name) {
    if (typeof name !== 'string' || !name.includes('/sessions/')) {
        return undefined;
    }
    const id = name.split('/').pop();
    if (!id) {
        return undefined;
    }
    return { id, resourceName: name };
}
function extractVertexOutputFallback(data) {
    if (data == null) {
        return '';
    }
    if (typeof data === 'string') {
        return data;
    }
    if (typeof data !== 'object') {
        return String(data);
    }
    const o = data;
    const tryString = (v) => {
        if (typeof v === 'string' && v.trim()) {
            return v;
        }
        return null;
    };
    const extractTextFromParts = (parts) => {
        if (!Array.isArray(parts)) {
            return null;
        }
        const texts = parts
            .map((part) => {
            if (!part || typeof part !== 'object') {
                return '';
            }
            const text = part.text;
            return typeof text === 'string' ? text : '';
        })
            .filter(Boolean);
        return texts.length > 0 ? texts.join('') : null;
    };
    const content = o.content;
    if (content && typeof content === 'object' && !Array.isArray(content)) {
        const fromContentParts = extractTextFromParts(content.parts);
        if (fromContentParts) {
            return fromContentParts;
        }
    }
    const fromParts = extractTextFromParts(o.parts);
    if (fromParts) {
        return fromParts;
    }
    for (const key of ['output', 'result', 'response', 'text', 'answer', 'content', 'message']) {
        const v = o[key];
        const s = tryString(v);
        if (s) {
            return s;
        }
        if (v && typeof v === 'object' && !Array.isArray(v)) {
            const inner = v;
            const fromInnerParts = extractTextFromParts(inner.parts);
            if (fromInnerParts) {
                return fromInnerParts;
            }
            const t = tryString(inner.text) ?? tryString(inner.content);
            if (t) {
                return t;
            }
        }
    }
    const actions = o.actions;
    if (actions && typeof actions === 'object' && !Array.isArray(actions)) {
        const stateDelta = actions.state_delta;
        if (stateDelta && typeof stateDelta === 'object' && !Array.isArray(stateDelta)) {
            for (const value of Object.values(stateDelta)) {
                const s = tryString(value);
                if (s) {
                    return s;
                }
            }
        }
    }
    if (o.output != null && typeof o.output === 'object') {
        const nested = extractVertexOutputFallback(o.output);
        if (nested) {
            return nested;
        }
    }
    try {
        return JSON.stringify(data);
    }
    catch {
        return String(data);
    }
}
/**
 * Parse `output` when it is a JSON string whose inner object has the final text in `output`
 * (common Reasoning Engine / agent flow pattern).
 */
function extractReasoningEngineResponseText(data) {
    if (data != null && typeof data === 'object' && !Array.isArray(data)) {
        const o = data;
        const out = o.output;
        if (typeof out === 'string') {
            const trimmed = out.trim();
            if (trimmed.startsWith('{') || trimmed.startsWith('[')) {
                try {
                    const inner = JSON.parse(out);
                    if (inner && typeof inner === 'object' && !Array.isArray(inner)) {
                        const io = inner;
                        if (typeof io.output === 'string' && io.output.trim()) {
                            return io.output.trim();
                        }
                    }
                }
                catch {
                    /* use raw string below */
                }
            }
            if (trimmed) {
                return trimmed;
            }
        }
    }
    return extractVertexOutputFallback(data);
}
/** Parse `data:` lines from Vertex `streamQuery` SSE (same idea as `vertex-example.mjs` `printSSE`). */
function parseSseDataEvents(sseBody) {
    const events = [];
    const lines = sseBody.split(/\r?\n/);
    for (const line of lines) {
        const trimmed = line.trim();
        if (!trimmed.toLowerCase().startsWith('data:')) {
            continue;
        }
        const payload = trimmed.replace(/^data:\s*/i, '').trim();
        if (!payload || payload === '[DONE]') {
            continue;
        }
        try {
            events.push(JSON.parse(payload));
        }
        catch {
            /* skip non-JSON fragments */
        }
    }
    return events;
}
function textFromStreamQueryBody(body) {
    const trimmed = body.trim();
    if (!trimmed) {
        return '';
    }
    const events = parseSseDataEvents(body);
    if (events.length > 0) {
        for (let i = events.length - 1; i >= 0; i -= 1) {
            const t = extractReasoningEngineResponseText(events[i]).trim();
            if (t) {
                return t;
            }
        }
        return extractVertexOutputFallback(events[events.length - 1]).trim();
    }
    if (trimmed.startsWith('{') || trimmed.startsWith('[')) {
        try {
            const j = JSON.parse(trimmed);
            return extractReasoningEngineResponseText(j);
        }
        catch {
            return trimmed;
        }
    }
    return trimmed;
}
/** Drain body with `getReader()` — same idea as `vertex-example.mjs` `printSSE` (avoid `res.json()` on streams / empty bodies). */
async function readResponseBodyUtf8(body) {
    if (!body) {
        return '';
    }
    const reader = body.getReader();
    const decoder = new TextDecoder();
    let out = '';
    try {
        for (;;) {
            const { done, value } = await reader.read();
            if (done) {
                break;
            }
            if (value && value.byteLength > 0) {
                out += decoder.decode(value, { stream: true });
            }
        }
    }
    finally {
        reader.releaseLock();
    }
    out += decoder.decode();
    return out;
}
class VertexAiAgentProvider {
    constructor(config, options = {}) {
        this.config = options.config ?? {};
        this.vertexConfig = config;
        const credentials = parseServiceAccountCredentials(config.serviceAccountKeyJson ?? '');
        this.queryUrl = buildStreamQueryUrl(config);
        const projectId = typeof credentials.project_id === 'string' ? credentials.project_id : undefined;
        this.auth = new google_auth_library_1.GoogleAuth({
            credentials: credentials,
            projectId,
            scopes: [CLOUD_PLATFORM_SCOPE],
        });
    }
    id() {
        return `vertex-ai-agent:${this.queryUrl}`;
    }
    async getAccessToken() {
        const client = await this.auth.getClient();
        const tokenResponse = await client.getAccessToken();
        const token = tokenResponse?.token;
        if (!token) {
            throw new Error('Vertex AI Agent: failed to obtain access token (empty token)');
        }
        return token;
    }
    /** Create a session via `:query` + `async_create_session` (ADK Agent Runtime REST). */
    async ensureSessionId(userId, headers) {
        const createPath = buildSyncQueryUrl(this.vertexConfig);
        const created = await (0, cache_1.fetchWithCache)(createPath, {
            method: 'POST',
            headers,
            body: JSON.stringify(buildCreateSessionBody(userId)),
        }, shared_1.REQUEST_TIMEOUT_MS, 'json', false, 0);
        if (created.status >= 400) {
            throw new Error(`Vertex AI Agent: create session failed: ${JSON.stringify(created)}`);
        }
        const output = created.data?.output;
        if (output && typeof output === 'object' && !Array.isArray(output)) {
            const sessionOutput = output;
            const id = sessionOutput.id;
            if (typeof id === 'string' && id.trim()) {
                return { id: id.trim() };
            }
            if (typeof id === 'number') {
                return { id: String(id) };
            }
        }
        if (created.status === 200 && created.data?.name) {
            return (sessionRefFromApiName(created.data.name) ?? {
                id: String(created.data.name).split('/').pop() ?? '',
            });
        }
        if (created.data?.name?.includes('/sessions/')) {
            return sessionRefFromApiName(created.data.name);
        }
        return undefined;
    }
    async callApi(prompt, _context) {
        try {
            const token = await this.getAccessToken();
            const headers = {
                Authorization: `Bearer ${token}`,
                'Content-Type': 'application/json',
            };
            const userId = this.config?.userId || `user_${Date.now()}`;
            const session = await this.ensureSessionId(userId, headers);
            if (!session?.id) {
                return {
                    error: `Vertex AI Agent: ensureSessionId did not return a session id.`,
                };
            }
            logger_1.default.debug(`[Vertex AI Agent]: session created: ${session.id}`);
            const sessionIdForStream = session.id;
            const queryBody = buildStreamQueryBody(userId, sessionIdForStream, prompt);
            logger_1.default.debug(`[Vertex AI Agent]: async_stream_query POST ${this.queryUrl}`);
            const res = await fetch(this.queryUrl, {
                method: 'POST',
                headers: {
                    Authorization: `Bearer ${token}`,
                    'Content-Type': 'application/json',
                    Accept: 'text/event-stream, application/json',
                },
                body: JSON.stringify(queryBody),
                signal: AbortSignal.timeout(shared_1.REQUEST_TIMEOUT_MS),
            });
            const rawFull = await readResponseBodyUtf8(res.body);
            if (!res.ok) {
                throw new Error(`streamQuery failed ${res.status}: ${rawFull.slice(0, 800)}`);
            }
            const serverHdr = res.headers.get('server') ?? '';
            const clen = res.headers.get('content-length');
            const trimmed = rawFull.trim();
            if (!trimmed) {
                return {
                    error: `Vertex AI Agent: streamQuery returned an empty body (Content-Length: ${clen ?? 'unknown'}). ` +
                        `If you see server "${serverHdr}" this is often a proxy or mock, not Vertex. Verify the POST URL is ` +
                        `https://<location>-aiplatform.googleapis.com/.../reasoningEngines/...:streamQuery and reachable from this host.`,
                    metadata: {
                        status: res.status,
                        sessionId: String(session.id),
                        contentType: res.headers.get('content-type') ?? undefined,
                        contentLength: clen ?? undefined,
                        server: serverHdr || undefined,
                    },
                };
            }
            const output = textFromStreamQueryBody(trimmed);
            return {
                output: output || trimmed,
                metadata: {
                    status: res.status,
                    sessionId: String(session.id),
                },
            };
        }
        catch (err) {
            logger_1.default.error(`[Vertex AI Agent]: ${String(err)}`);
            return {
                error: `Vertex AI Agent error: ${err instanceof Error ? err.message : String(err)}`,
            };
        }
    }
}
exports.VertexAiAgentProvider = VertexAiAgentProvider;
//# sourceMappingURL=vertex-ai-agent.js.map