API

The composable, the hooks, and the page and link attributes.

usePrecog()

const { enabled, pause, resume, refresh, grantConsent, metrics, plan } = usePrecog();
MemberWhat it does
enabledFalse while paused, or before consent when privacy.requireConsent is on.
pause()Stops predicting and removes the speculation rules.
resume()Starts again.
refresh()Predicts now, ignoring minIntervalMs.
grantConsent()Lets the module run when privacy.requireConsent is on.
metricsCounters, latency percentiles, hit rate and the estimated cost.
planWhat is speculated right now, or null.
pause() stays paused until you call resume(). A navigation will not undo it, which is a deliberate change from an earlier version that silently resumed on every route change.

On the server, and when the module is off, usePrecog() returns a no-op object, so a page can call it unconditionally.

Per page

<script setup>
definePageMeta({ precog: false });
</script>

The module collects nothing and speculates nothing while this page is open, and picks up again when the visitor leaves it.

<template>
  <NuxtLink to="/settings" data-precog="off">Never speculate this</NuxtLink>
  <NuxtLink to="/pricing" data-precog="hint">Always a candidate, even off screen</NuxtLink>
</template>

Client hooks

nuxtApp.hook("precog:decision", (plan, trigger) => {
  // trigger is 'route' | 'scroll' | 'pointer' | 'dom' | 'manual' | 'visible'
});

nuxtApp.hook("precog:metrics", (summary) => {
  // summary.hitRate, summary.p95, summary.inputTokens, ...
});

A route change fires precog:decision with an empty plan, because the links the old plan named are gone.

Server hooks

In a Nitro plugin:

server/plugins/precog.ts
export default defineNitroPlugin((nitro) => {
  nitro.hooks.hook("precog:predict", (ctx) => {
    // Read ctx.state, or set ctx.prediction to answer without calling Jev at all.
  });

  nitro.hooks.hook("precog:predicted", (ctx) => {
    // Every fresh answer. ctx.substituted is true when it came from the hook above.
  });
});

precog:predict is how the playground runs with no API key, and how you would swap in your own model. precog:predicted is how it records real answers for replay.

A precog:predicted listener that throws is ignored: the prediction already happened and the visitor gets it. A precog:predict listener that throws fails the request, because that hook is allowed to replace the answer.

When a prediction fails, the endpoint answers 503 with an empty prediction and puts a short reason in the x-precog-reason header. The client falls open either way.

Types

import type {
  PrecogPlan,
  PrecogPrediction,
  PrecogState,
  PrecogCandidate,
  MetricsSummary,
} from "nuxt-precog";