/** * LlmMeteringPlugin * * Opt-in billing integration for graphile-llm. Completely separate from the * pure LLM plugins (text-search, text-mutation, rag). * * **How it works:** * 1. At schema build time, replaces `build.llmEmbedder` with a metered wrapper * that has the same `(text: string) => Promise` signature * 2. At request time, wraps every root query/mutation resolver to set up a * request-scoped MeteringContext via AsyncLocalStorage * 3. When the embedder is called (by any plugin), the wrapper checks * AsyncLocalStorage for a metering context and if found, calls * check_billing_quota before and record_usage after * 4. If quota is exceeded, the wrapper returns null — the calling plugin sees * null and handles it (search falls back to text-only, mutations throw) * * The pure plugins never import metering, config-cache, or billing types. * They call the embedder and handle null results — that's it. * * **Entity ID resolution:** * The billing `entity_id` is resolved via a configurable callback. * Default: reads `jwt.claims.user_id` from pgSettings. Override via * `metering.resolveEntityId` in GraphileLlmPreset options. * * **Graceful behavior:** * - billing_module not provisioned → embedder passes through unmetered * - entity_id not available → embedder passes through unmetered * - check_billing_quota throws → call is allowed (billing is opt-in) * - record_usage throws → call succeeds, recording silently skipped * - quota exceeded → embedder returns null */ import type { GraphileConfig } from 'graphile-config'; import type { MeteringConfig } from '../types'; declare global { namespace GraphileConfig { interface Plugins { LlmMeteringPlugin: true; } } } export declare function createLlmMeteringPlugin(meteringConfig?: MeteringConfig): GraphileConfig.Plugin;