* chore: expose meteringService methods in wrapper while not in an extension * docs: metering and examples
4.6 KiB
Metering Service
This service provides all metering functionality in puter. It relies on our own KV infrastructure to track usage (note the implementation of kvStore affects performance, and atomicity, currently sqlite implementation is not atomic).
It will also slowly add functionality around credit purchasing in the future, but for now it is just metering and usage. This should be the primary, and ideally only, way to check for usage and record it.
Usage
Within Core Modules
To use the metering service within core modules, you can access it via the services object. Here's an example of how to check if an actor has enough credits for a specific usage type:
class SomeCoreModule extends BaseService {
get #meteringService(): MeteringService {
return this.services.get('meteringService') as MeteringService;
}
async someMeteredFunction(actor: Actor) {
const hasEnoughCredits = await this.#meteringService.hasEnoughCreditsFor(actor, 'someUsageKey:units', 1000);
// ...
const updatedUsage = await this.#meteringService.incrementUsage(actor, 'someUsageKey:units', 1000);
}
}
Note you don't have to structure like that if you don't want, but it's a nice way to encapsulate the service access. You can also do:
const meteringService = this.services.get('meteringService') as MeteringService;
// or
const meteringService = Context.get('services').get('meteringService') as MeteringService;
or any other way you like to access services.
Within Extensions
To use the metering service within extensions, you can import it using the extension's import service method
/** @type {import('@heyputer/backend/src/services/MeteringService/MeteringServiceWrapper.mjs').MeteringServiceWrapper} */
const meteringService = extension.import('service:meteringService');
Note on imports
Due to the way we structure services, when importing the metering service in extensions, you get the MeteringServiceWrapper class. This is a bit of a middlestep while MeteringService is not an extension itself. Which is why you'll see some places doing:
const meteringService = this.services.get('meteringService').meteringService as MeteringService
but for usability, those same methods are exposed directly on the wrapper so you don't need to do that.
Cost maps
The metering service relies on cost maps to determine how much to charge for a given operation. Cost maps are simple JSON objects that map a usage type to a cost per unit in microcents (1 millionth of a cent). For example, a cost map for AWS Polly might look like this:
{
"aws-polly:standard:character": 4,
"aws-polly:neural:character": 16
}
We need to manually update these for now until we can automate it somehow. You can add more costs to the cost map as needed.
Cost overrides
In some cases, you may want to override the default cost for a specific actor, or give a cost if not provided in the cost map. you can do this by passing in the cost override when incrementing usage:
await meteringService.incrementUsage(actor, 'someUnmappedOperation:units', 1000, 5000000); // override cost to 5 cents = 5 million microcents for the whole 1000 units
Other util methods
See MeteringService.ts for more details on how metering works. Its all typescript so you can always just get intellisense on the methods.
Adding and Getting User Subscription Plans
Though the metering service itself doesn't handle subscriptions nor credit purchases (yet at least), it does emit events for extensions to provide them with the necessary data to limit usage for users. These following events are emitted:
metering:overrideDefaultSubscription- allows extension to override the default subscription plan for a usermetering:registerAvailablePolicies- allows extension to register available subscription policies/plansmetering:getUserSubscription- allows extension to provide the current subscription plan for a user For example on these see the extension meteringAndBilling for how to use these events to provide subscription plans.
Examples
Core Module example
See OpenAI module for an example of how to use the metering service within a core module: OpenAICompletionService.mjs
Extension example
See meteringAndBilling extension for an example of how to use the metering service within an extension: usage.js