/**
 * HTTP client wrapper for `hippo serve`.
 *
 * Mirrors the function signatures in src/api.ts so the CLI can route through
 * either path uniformly. Each function takes (serverUrl, apiKey?, ...) and
 * returns the same shape that api.ts would.
 *
 * Errors from the server (4xx/5xx) are mapped back into thrown Errors with
 * the server's `error` message preserved verbatim so existing CLI handlers
 * that match on substrings (e.g. "not found", "already superseded") still
 * work unchanged.
 *
 * Network errors (ECONNREFUSED on a stale pidfile, etc.) propagate as the
 * native fetch failure so the caller can detect them and self-heal.
 */
function buildHeaders(apiKey, withBody) {
    const headers = {};
    if (withBody)
        headers['content-type'] = 'application/json';
    if (apiKey)
        headers['authorization'] = `Bearer ${apiKey}`;
    return headers;
}
/**
 * Throw an Error matching the server's error message. Keeps message strings
 * intact so cli.ts handlers can match on the same substrings ("not found",
 * "already superseded", "Unknown key_id") whether the call went through
 * api.ts or client.ts.
 */
async function throwForStatus(res) {
    let message = `${res.status} ${res.statusText}`;
    try {
        const body = await res.json();
        if (body && typeof body.error === 'string' && body.error.length > 0) {
            message = body.error;
        }
    }
    catch {
        // body wasn't JSON; fall back to status line.
    }
    throw new Error(message);
}
export async function remember(serverUrl, apiKey, opts) {
    const res = await fetch(`${serverUrl}/v1/memories`, {
        method: 'POST',
        headers: buildHeaders(apiKey, true),
        body: JSON.stringify(opts),
    });
    if (!res.ok)
        await throwForStatus(res);
    return await res.json();
}
export async function recall(serverUrl, apiKey, opts) {
    const params = new URLSearchParams();
    params.set('q', opts.query);
    if (opts.limit !== undefined)
        params.set('limit', String(opts.limit));
    if (opts.mode !== undefined)
        params.set('mode', opts.mode);
    if (opts.scope !== undefined && opts.scope !== '')
        params.set('scope', opts.scope);
    if (opts.includeContinuity)
        params.set('include_continuity', '1');
    // v1.7.2 T4 — RecallOpts parity sweep. Pre-v1.7.2 the thin-client only
    // serialized the five fields above; the HTTP server already accepted
    // fresh_tail_count, fresh_tail_session_id, summarize_overflow. Adding
    // scorer_window alone would have perpetuated the drift. Serializing all
    // four together. Validation lives in api.recall(); transport just
    // forwards.
    if (opts.freshTailCount !== undefined)
        params.set('fresh_tail_count', String(opts.freshTailCount));
    if (opts.freshTailSessionId !== undefined && opts.freshTailSessionId !== '')
        params.set('fresh_tail_session_id', opts.freshTailSessionId);
    if (opts.summarizeOverflow !== undefined)
        params.set('summarize_overflow', opts.summarizeOverflow ? '1' : '0');
    if (opts.scorerWindow !== undefined)
        params.set('scorer_window', String(opts.scorerWindow));
    // v1.7.4 sessionId for dlPFC goal-stack boost + v0.33 / J1 sessionId
    // for per-session anchoring ring. Pre-v0.33 the thin-client silently
    // dropped opts.sessionId, so HTTP-routed SDK callers got no goal
    // boost and (post-J1) the HTTP ring logged recall_anchor_skipped_no_session.
    // Codex round-3 P2 catch: align serialization with the RecallOpts type.
    // Note: opts.recallHistory is intentionally NOT serialized — it is an
    // in-process snapshot only; the HTTP server maintains its own ring per
    // sessionId, so passing recallHistory over the wire would be a no-op
    // at best and a tenant-leak risk at worst.
    if (opts.sessionId !== undefined && opts.sessionId !== '') {
        params.set('session_id', opts.sessionId);
    }
    const res = await fetch(`${serverUrl}/v1/memories?${params.toString()}`, {
        method: 'GET',
        headers: buildHeaders(apiKey, false),
    });
    if (!res.ok)
        await throwForStatus(res);
    return await res.json();
}
export async function forget(serverUrl, apiKey, id) {
    const res = await fetch(`${serverUrl}/v1/memories/${encodeURIComponent(id)}`, {
        method: 'DELETE',
        headers: buildHeaders(apiKey, false),
    });
    if (!res.ok)
        await throwForStatus(res);
    return await res.json();
}
export async function promote(serverUrl, apiKey, id) {
    const res = await fetch(`${serverUrl}/v1/memories/${encodeURIComponent(id)}/promote`, {
        method: 'POST',
        headers: buildHeaders(apiKey, false),
    });
    if (!res.ok)
        await throwForStatus(res);
    return await res.json();
}
export async function supersede(serverUrl, apiKey, oldId, newContent) {
    const res = await fetch(`${serverUrl}/v1/memories/${encodeURIComponent(oldId)}/supersede`, {
        method: 'POST',
        headers: buildHeaders(apiKey, true),
        body: JSON.stringify({ content: newContent }),
    });
    if (!res.ok)
        await throwForStatus(res);
    return await res.json();
}
export async function archiveRaw(serverUrl, apiKey, id, reason) {
    const res = await fetch(`${serverUrl}/v1/memories/${encodeURIComponent(id)}/archive`, {
        method: 'POST',
        headers: buildHeaders(apiKey, true),
        body: JSON.stringify({ reason }),
    });
    if (!res.ok)
        await throwForStatus(res);
    return await res.json();
}
export async function authCreate(serverUrl, apiKey, opts) {
    const res = await fetch(`${serverUrl}/v1/auth/keys`, {
        method: 'POST',
        headers: buildHeaders(apiKey, true),
        body: JSON.stringify(opts),
    });
    if (!res.ok)
        await throwForStatus(res);
    return await res.json();
}
export async function authList(serverUrl, apiKey, opts) {
    const params = new URLSearchParams();
    params.set('active', opts.active ? 'true' : 'false');
    const res = await fetch(`${serverUrl}/v1/auth/keys?${params.toString()}`, {
        method: 'GET',
        headers: buildHeaders(apiKey, false),
    });
    if (!res.ok)
        await throwForStatus(res);
    return await res.json();
}
export async function authRevoke(serverUrl, apiKey, keyId) {
    const res = await fetch(`${serverUrl}/v1/auth/keys/${encodeURIComponent(keyId)}`, {
        method: 'DELETE',
        headers: buildHeaders(apiKey, false),
    });
    if (!res.ok)
        await throwForStatus(res);
    return await res.json();
}
export async function auditList(serverUrl, apiKey, opts) {
    const params = new URLSearchParams();
    if (opts.op !== undefined)
        params.set('op', opts.op);
    if (opts.since !== undefined)
        params.set('since', opts.since);
    if (opts.limit !== undefined)
        params.set('limit', String(opts.limit));
    const qs = params.toString();
    const url = qs.length > 0 ? `${serverUrl}/v1/audit?${qs}` : `${serverUrl}/v1/audit`;
    const res = await fetch(url, {
        method: 'GET',
        headers: buildHeaders(apiKey, false),
    });
    if (!res.ok)
        await throwForStatus(res);
    return await res.json();
}
/**
 * True for fetch failures that look like "server not actually running" (the
 * pidfile said one was, but the connection refused, or DNS / abort errors).
 * The CLI uses this to detect a stale pidfile and self-heal back to direct mode.
 */
export function isConnectionRefused(err) {
    if (!(err instanceof Error))
        return false;
    const message = err.message.toLowerCase();
    // Node fetch wraps the underlying cause; the surface message contains 'fetch failed'
    // and the cause has the syscall code. We check both shapes.
    if (message.includes('econnrefused'))
        return true;
    if (message.includes('connect econnrefused'))
        return true;
    const cause = err.cause;
    if (cause && typeof cause === 'object') {
        const code = cause.code;
        if (code === 'ECONNREFUSED' || code === 'ECONNRESET')
            return true;
    }
    // Fallthrough: 'fetch failed' alone is suspicious. Treat as connection failure
    // so the CLI heals on a stale pidfile rather than surfacing a cryptic error.
    if (message.includes('fetch failed'))
        return true;
    return false;
}
//# sourceMappingURL=client.js.map