import { Logger } from "@nestjs/common"; import { ConfigService } from "@nestjs/config"; import { EventEmitter2 } from "@nestjs/event-emitter"; import { ClsService } from "nestjs-cls"; import { BaseConfigInterface, ConfigAiInterface } from "../../../config/interfaces"; import { TokenUsageInterface } from "../../../common/interfaces/token.usage.interface"; import { JsonApiService } from "../../../core/jsonapi/services/jsonapi.service"; import { AbstractService } from "../../../core/neo4j/abstracts/abstract.service"; import { TokenUsage, TokenUsageDescriptor } from "../../tokenusage/entities/tokenusage"; import { TokenUsageAggregated, TokenUsageRepository, TokenUsageSummary } from "../../tokenusage/repositories/tokenusage.repository"; import { ModelWeight } from "../../../core/llm/enums/model.weight"; /** * Per-call cost rates, in euros per 1M tokens, belonging to the AI connection * that ACTUALLY served a call. * * Exists because a call may be served by any link of a DB-backed fallback chain * (`AiConnection`), and each link carries its own prices — so the config block * for the tier is no longer necessarily the right rate card. Supplied ONLY for * candidates whose `source` is `"db"`; an `.env`-sourced candidate passes * nothing and keeps the config-block path exactly as before. */ export interface TokenUsageRatesInterface { inputCostPer1MTokens?: number; outputCostPer1MTokens?: number; cachedInputCostPer1MTokens?: number; } /** * TokenUsage service. * * Extends `AbstractService` so a consuming application can subclass it (see * `ExtendedTokenUsageService` in a consuming app) and have BOTH the inherited * generic methods AND every method declared here serialise with the *extended* * model. Model resolution is by subclass polymorphism — a subclass re-declares * `descriptor` and `model` as initialised class fields — never by a registry * lookup (Nest constructs providers before `onModuleInit`, where models are * registered). * * Every member is `protected` rather than `private` precisely so a subclass can * reuse it: TypeScript forbids a subclass from redeclaring a name a base class * holds privately (TS2415), which would otherwise force the extension to invent * aliases for its own constructor parameters. * * Inherited generic CRUD (find / findById / create / put / patch / delete) is * unused by any HTTP route in the package — there is no tokenusage controller, * records are only ever written through `recordTokenUsage()` — but is available * to consuming apps that mount their own controller. */ export declare class TokenUsageService extends AbstractService { protected readonly tokenUsageRepository: TokenUsageRepository; protected readonly configService: ConfigService; protected readonly eventEmitter: EventEmitter2; protected readonly descriptor: import("../../..").EntityDescriptor; protected readonly logger: Logger; constructor(jsonApiService: JsonApiService, tokenUsageRepository: TokenUsageRepository, clsService: ClsService, configService: ConfigService, eventEmitter: EventEmitter2); protected get aiConfig(): ConfigAiInterface; protected configForWeight(weight?: ModelWeight): import("../../../config/interfaces").AiTierConfig; /** * Computes the monetary cost of a call from the per-tier rates in config * (`inputCostPer1MTokens` / `outputCostPer1MTokens`). Single source of truth — * used both for persistence and for surfacing cost in ephemeral telemetry. * * `rates` overrides those config rates with the ones belonging to the AI * connection that actually served the call (see * {@link TokenUsageRatesInterface}). It is passed ONLY for DB-backed * connections, so a caller that never configures any keeps the config-block * path bit-for-bit. Each rate falls back to the config block individually, so * a connection that prices only some fields still bills the rest at the tier * rate — EXCEPT the cached rate, which falls back to the EFFECTIVE input rate * rather than the config's cached rate: mixing one connection's input price * with another's cache discount would price cached tokens above uncached ones. */ /** * The CONFIG tier whose rates price a call — and, when no DB-backed connection * supplied its own, the tier that served it. * * Extracted so `computeCost` and the recorded `model`/`provider` read the same * tier: a record billed at the vision rate but labelled with the base model * would be worse than an unlabelled one, because it reads as evidence. * * NOTE: this resolves the CONFIG BLOCK only. When a DB-backed AI connection * serves the call it also carries its own `rates`, and its model is NOT this * tier's model — such callers must pass `model`/`provider` explicitly, exactly * as the `costOverride` callers do. */ protected configForCall(params: { useVisionCosts?: boolean; modelWeight?: ModelWeight; }): { provider: string; apiKey: string; model: string; url: string; region?: string; secret?: string; instance?: string; apiVersion?: string; inputCostPer1MTokens: number; outputCostPer1MTokens: number; googleCredentialsBase64?: string; reasoningEffort?: string; }; computeCost(params: { tokens: TokenUsageInterface; useVisionCosts?: boolean; modelWeight?: ModelWeight; /** Rates of the connection that served the call; overrides the config block. */ rates?: TokenUsageRatesInterface; }): number; /** * Persists one usage record and converts its monetary cost into billing * credits: `credits = max(minCreditsPerRecord, round4(cost / creditCost))`. * `creditCost` absent or 0 disables credits entirely (records store 0 and no * balance is deducted), so consumers without a `credits` config are unaffected. * * Rounded to 4 decimals (not 2): sub-cent operations (a ~144-token * transcription utterance, an embedding call) previously rounded to 0.00 at * this step even before the per-record floor was applied, so cheap, * high-volume calls billed nothing. 4dp keeps them measurable. */ recordTokenUsage(params: { tokens: TokenUsageInterface; type: string; relationshipId: string; relationshipType: string; useVisionCosts?: boolean; modelWeight?: ModelWeight; /** When false the per-record credits floor (`minCreditsPerRecord`) is skipped — sub-cent calls (embeddings) must not be floored. Defaults to true. */ applyMinimum?: boolean; /** Pre-computed cost in euros; when provided `computeCost` is skipped (embeddings: estimated tokens × the embedder rate). */ costOverride?: number; /** Rates of the AI connection that served the call; forwarded to `computeCost`. */ rates?: TokenUsageRatesInterface; /** * The model that served the call. MUST be supplied by callers whose model is * not the config tier's: those pricing with `costOverride` (embeddings, * transcription), and those served by a DB-backed AI connection (which also * pass `rates`). Without it the record would name the config-block model for * a call that model never made. Everyone else omits it and gets the tier that * priced the call. */ model?: string; /** Provider that served the call. Same rule as `model`. */ provider?: string; }): Promise; /** * KEPT (custom filtering not covered by inherited find()): date-range + tokenUsageType * filtering. Backs GET /tokenusages in consuming apps. */ findByCompany(params: { query: any; startDate?: string; endDate?: string; tokenUsageType?: string; }): Promise; /** * KEPT (raw aggregation, not JSON:API entities — inherited find()/findById() can't * produce this shape). Backs GET /tokenusages/aggregated in consuming apps. */ getUsageByDateAndType(params: { startDate?: string; endDate?: string; }): Promise; /** * KEPT (raw aggregation — see getUsageByDateAndType above). Backs GET * /tokenusages/summary in consuming apps. */ getUsageSummary(params: { startDate?: string; endDate?: string; }): Promise; } //# sourceMappingURL=tokenusage.service.d.ts.map