/**
 * Antigravity cloud provider — Google's Antigravity agent via the Gemini
 * **Managed Agents Interactions API**.
 *
 * The `agy` CLI is local-only (`--print` / `--sandbox`); Antigravity's cloud
 * surface is an HTTP endpoint that runs the Antigravity harness in a remote
 * ephemeral Linux sandbox:
 *
 *   POST https://generativelanguage.googleapis.com/v1beta/interactions
 *   x-goog-api-key: <GEMINI_API_KEY>
 *   { "agent": "antigravity-preview-05-2026", "input": "<prompt>", "environment": "remote" }
 *
 * The call is synchronous by default (the response carries `status` +
 * `output_text`), so `dispatch()` awaits it, returns a terminal CloudTask, and
 * `stream()` replays the buffered text + done events — same shape as the Factory
 * provider. It is a raw sandbox: no GitHub repo → PR (that's Rush's job).
 *
 * Auth: the Gemini API key comes from an `agents secrets` bundle named in
 * `cloud.providers.antigravity.secretsBundle` (never from agents.yaml), falling
 * back to GEMINI_API_KEY / GOOGLE_API_KEY in the environment.
 */
import { resolveDispatchRepos, normalizeProviderStatus } from './types.js';
import { readAndResolveBundleEnv } from '../secrets/bundles.js';
const INTERACTIONS_URL = 'https://generativelanguage.googleapis.com/v1beta/interactions';
const DEFAULT_MODEL = 'antigravity-preview-05-2026';
const KEY_NAMES = ['GEMINI_API_KEY', 'GOOGLE_API_KEY'];
/** Build the Interactions API request body for a fresh dispatch. */
export function buildInteractionBody(prompt, model) {
    return {
        agent: model,
        input: prompt,
        environment: 'remote',
    };
}
/** Parse an Interactions API response into a CloudTask (minus prompt/timestamps). */
export function parseInteraction(resp) {
    const id = resp.id ?? resp.interaction_id ?? `antigravity-${Date.now()}`;
    return {
        id,
        status: normalizeProviderStatus('antigravity', resp.status),
        summary: resp.output_text,
        environmentId: resp.environment_id,
    };
}
export class AntigravityCloudProvider {
    id = 'antigravity';
    name = 'Antigravity (Gemini)';
    secretsBundle;
    model;
    runs = new Map();
    constructor(config) {
        this.secretsBundle = config?.secretsBundle;
        this.model = config?.model ?? DEFAULT_MODEL;
    }
    /**
     * True when a key *source* is configured. Cheap: never resolves the bundle
     * (which could prompt for biometry) — that happens lazily at dispatch.
     */
    hasKeySource() {
        if (this.secretsBundle)
            return true;
        return KEY_NAMES.some((k) => Boolean(process.env[k]));
    }
    /** Resolve the Gemini API key from the configured bundle or the environment. */
    resolveApiKey() {
        if (this.secretsBundle) {
            try {
                // Cloud dispatch resolves the key on its own (no human at a sheet), so the
                // read is always `agentOnly` (SEC-13: never pop Touch ID on its own). A
                // locked bundle THROWS the actionable "unlock <name>" message, which the
                // catch below re-raises verbatim — dispatch genuinely needs the key, so it
                // fails LOUD with the unlock hint rather than swallowing it into a wrong
                // path. A `never`/no-ACL or broker-held bundle resolves silently.
                const { env } = readAndResolveBundleEnv(this.secretsBundle, { caller: 'cloud:antigravity', agentOnly: true });
                for (const k of KEY_NAMES) {
                    if (env[k])
                        return env[k];
                }
                throw new Error(`Secrets bundle '${this.secretsBundle}' has no ${KEY_NAMES.join(' or ')}. Add one: agents secrets add ${this.secretsBundle} GEMINI_API_KEY`);
            }
            catch (err) {
                throw new Error(`Could not read Gemini API key from bundle '${this.secretsBundle}': ${err.message}`);
            }
        }
        for (const k of KEY_NAMES) {
            const v = process.env[k];
            if (v)
                return v;
        }
        throw new Error(`Antigravity cloud needs a Gemini API key. Set cloud.providers.antigravity.secretsBundle in ~/.agents/agents.yaml, or export ${KEY_NAMES.join(' / ')}.`);
    }
    capabilities() {
        const available = this.hasKeySource();
        return {
            available,
            dispatch: available,
            status: available,
            list: available,
            stream: available,
            cancel: false,
            message: false,
            multiRepo: false,
            skills: false,
            images: false,
        };
    }
    async dispatch(options) {
        // The Interactions sandbox has no GitHub repo → PR flow. Reject repos
        // loudly rather than silently ignoring them (point the user at Rush).
        const repos = resolveDispatchRepos(options);
        if (repos.length > 0) {
            throw new Error(`Antigravity cloud is a raw sandbox with no GitHub repo → PR flow. Got repo(s): ${repos.join(', ')}. ` +
                `Use --provider rush for repo-backed dispatch.`);
        }
        const apiKey = this.resolveApiKey();
        const model = options.model ?? this.model;
        const body = buildInteractionBody(options.prompt, model);
        let resp;
        try {
            resp = await fetch(INTERACTIONS_URL, {
                method: 'POST',
                headers: { 'content-type': 'application/json', 'x-goog-api-key': apiKey },
                body: JSON.stringify(body),
            });
        }
        catch (err) {
            throw new Error(`Antigravity dispatch failed (network): ${err.message}`);
        }
        const text = await resp.text();
        if (!resp.ok) {
            throw new Error(`Antigravity dispatch failed (${resp.status}): ${text.slice(0, 500)}`);
        }
        let parsed;
        try {
            parsed = JSON.parse(text);
        }
        catch {
            throw new Error(`Antigravity returned non-JSON response: ${text.slice(0, 300)}`);
        }
        const { id, status, summary } = parseInteraction(parsed);
        const now = new Date().toISOString();
        const task = {
            id,
            provider: 'antigravity',
            status,
            agent: 'antigravity',
            prompt: options.prompt,
            summary,
            createdAt: now,
            updatedAt: now,
        };
        const events = [];
        if (summary)
            events.push({ type: 'text', content: summary, timestamp: now });
        events.push({ type: 'done', status, summary, timestamp: now });
        this.runs.set(id, { events, task });
        return task;
    }
    async status(taskId) {
        const run = this.runs.get(taskId);
        if (run)
            return run.task;
        throw new Error(`No live status for Antigravity task ${taskId} (synchronous interaction; see local cache).`);
    }
    async list() {
        return [...this.runs.values()].map((r) => r.task);
    }
    async *stream(taskId) {
        const run = this.runs.get(taskId);
        if (!run) {
            yield {
                type: 'error',
                message: `Antigravity interaction ${taskId} is not buffered in this process. The interaction is synchronous — see 'agents cloud status ${taskId}' for the result.`,
                timestamp: new Date().toISOString(),
            };
            return;
        }
        for (const event of run.events)
            yield event;
    }
    async cancel(_taskId) {
        throw new Error('Cancel is not supported for Antigravity — interactions run synchronously to completion.');
    }
    async message(_taskId, _content) {
        throw new Error('Follow-up messages are not yet supported for Antigravity cloud tasks.');
    }
}
