/**
 * Literacy TraceAdapter (D0088 slice 2).
 *
 * Literacy runs our own Anthropic/OpenAI tool loop (`agentic-provider.ts`).
 * Unlike MCP — which exposes a single `metadata.toolCalls` provenance — the
 * literacy trace fuses two views into one ordered `toolCalls` array:
 *
 *   - the model's `tool_use` transcript (`metadata.toolCalls`,
 *     `LiteracyToolCall[]`) → `loop-transcript` calls. These ARE the request
 *     sequence: what the model decided to do, in turn order.
 *   - the agent-observer HTTP log (`metadata.agentBehavior`) → `http-observer`
 *     calls synthesized as `WebSearch` (one per `searchQueries[]`) and
 *     `WebFetch` (one per `docPageVisits[]`). These carry the authoritative
 *     visited doc URLs/slugs and search terms, which the collector's
 *     access-pattern extraction turns into `searchTerms` / `urlsVisited`.
 *
 * Per-turn LLM latency (`metadata.llmTimings`) is forwarded so the collector
 * builds `llm-turn` timeline spans. v1 captures tool calls + timing + HTTP
 * only — no extended-thinking content (D0088).
 *
 * The agent-observer session's real wall-clock bounds
 * (`AgentBehaviorLog.startedAt`/`endedAt`) are forwarded as the trace's
 * `startedAt`/`completedAt` so the persisted window reflects execution time,
 * not emission time (W-trace-real-timestamps).
 *
 * @see packages/eval/src/agent-observer/agentic-provider.ts
 * @see packages/eval/src/agent-observer/types.ts (AgentBehaviorLog, LiteracyToolCall)
 */
function isRecord(value) {
    return typeof value === "object" && value !== null && !Array.isArray(value);
}
function numberArrayOrUndefined(value) {
    return Array.isArray(value) && value.every((n) => typeof n === "number")
        ? value
        : undefined;
}
/**
 * Merge the two places promptfoo can surface provider metadata. The literacy
 * provider returns it on `ProviderResponse.metadata`, which promptfoo lifts to
 * the top-level `result.metadata` (where `calculate-scores` reads
 * `agentBehavior`); some paths also leave a copy on `response.metadata`.
 * Top-level wins so the lifted copy is preferred.
 */
function metadataOf(native) {
    return { ...(native.response?.metadata ?? {}), ...(native.metadata ?? {}) };
}
/**
 * Map one loop-transcript entry into a `loop-transcript` RawToolCall.
 * `durationMs`/`turnIndex` (W-agentic-tool-call-spans) let the collector's
 * `buildSpans` interleave `tool-call-{i}` spans with the `llm-turn-{i}`
 * spans; synthesized `http-observer` calls never carry them, so they
 * produce no spans.
 */
function toLoopCall(entry) {
    return {
        name: entry.name,
        input: isRecord(entry.input) ? entry.input : {},
        output: entry.output,
        ...(entry.error !== undefined ? { error: entry.error } : {}),
        ...(entry.durationMs !== undefined ? { durationMs: entry.durationMs } : {}),
        ...(entry.turnIndex !== undefined ? { turnIndex: entry.turnIndex } : {}),
        source: "loop-transcript",
    };
}
/**
 * Synthesize `http-observer` RawToolCalls from the agent-observer log: one
 * `WebSearch` per recorded search query, one `WebFetch` per doc-page visit.
 * The names classify to `search` / `read` so the collector lifts the query →
 * `searchTerms` and the url → `urlsVisited`. Ordered by observation timestamp
 * so the network view reads chronologically.
 */
function toHttpObserverCalls(behavior) {
    const searches = (behavior.searchQueries ?? []).map((q) => ({
        timestamp: q.timestamp,
        call: {
            name: "WebSearch",
            input: {
                query: q.query,
                ...(q.url !== undefined ? { url: q.url } : {}),
            },
            output: { resultCount: q.resultCount ?? null },
            source: "http-observer",
        },
    }));
    const visits = (behavior.docPageVisits ?? []).map((v) => ({
        timestamp: v.timestamp,
        call: {
            name: "WebFetch",
            input: { url: v.url, slug: v.slug },
            output: {
                ...(v.title !== undefined ? { title: v.title } : {}),
                contentSize: v.contentSize,
            },
            source: "http-observer",
        },
    }));
    return [...searches, ...visits]
        .sort((a, b) => a.timestamp < b.timestamp ? -1 : a.timestamp > b.timestamp ? 1 : 0)
        .map((entry) => entry.call);
}
export const literacyTraceAdapter = {
    mode: "literacy",
    toRawTrace(native) {
        const meta = metadataOf(native);
        const loopEntries = Array.isArray(meta.toolCalls)
            ? meta.toolCalls
            : [];
        const loopCalls = loopEntries.map(toLoopCall);
        const behavior = isRecord(meta.agentBehavior)
            ? meta.agentBehavior
            : undefined;
        const httpCalls = behavior ? toHttpObserverCalls(behavior) : [];
        const llmTimings = numberArrayOrUndefined(meta.llmTimings);
        return {
            ...(native.response?.tokenUsage
                ? { tokenUsage: native.response.tokenUsage }
                : {}),
            ...(native.latencyMs !== undefined
                ? { latencyMs: native.latencyMs }
                : {}),
            // W-trace-real-timestamps — forward the agent-observer session's real
            // wall-clock bounds so the persisted trace's window reflects execution
            // time, not artifact-emission time. `startedAt`/`endedAt` are required on
            // `AgentBehaviorLog` (proxy `start()`/`stop()`), so when the log is
            // present both flow through and the collector uses them verbatim.
            ...(behavior?.startedAt ? { startedAt: behavior.startedAt } : {}),
            ...(behavior?.endedAt ? { completedAt: behavior.endedAt } : {}),
            metadata: {
                toolCalls: [...loopCalls, ...httpCalls],
                ...(llmTimings ? { llmTimings } : {}),
            },
        };
    },
};
