PromptOpsKit includes an optional promptopskit/usagetap helper layer for UsageTap.com call tracking. It wraps your own provider call with call_begin and call_end requests while keeping the core prompt rendering API transport-light.
UsageTap helpers ship inside the main package under a separate subpath:
import {
createUsageTapClient,
runOpenAIWithUsageTap,
runOpenRouterWithUsageTap,
runLLMAsAServiceWithUsageTap,
runAnthropicWithUsageTap,
runGeminiWithUsageTap,
withUsageTapCall,
extractOpenAIUsage,
extractAnthropicUsage,
extractGeminiUsage,
} from 'promptopskit/usagetap';The helper layer does not add an SDK dependency. It uses fetch directly against https://api.usagetap.com and sends:
Authorization: Bearer ...Accept: application/vnd.usagetap.v1+jsonContent-Type: application/json
import { createUsageTapClient } from 'promptopskit/usagetap';
const usageTap = createUsageTapClient({
apiKey: process.env.USAGETAP_API_KEY!,
});You can also override baseUrl or fetch for tests and custom runtimes.
import { createPromptOpsKit } from 'promptopskit';
import { createUsageTapClient, runOpenAIWithUsageTap } from 'promptopskit/usagetap';
const kit = createPromptOpsKit({ sourceDir: './prompts' });
const usageTap = createUsageTapClient({ apiKey: process.env.USAGETAP_API_KEY! });
const result = await kit.renderPrompt({
path: 'support/reply',
provider: 'openai',
variables: {
user_message: 'How do I reset my password?',
app_context: 'Account settings page',
},
});
if (!result.request) {
throw new Error(result.returnMessage ?? 'Prompt rendering failed.');
}
const { request } = result;
const tracked = await runOpenAIWithUsageTap(usageTap, {
begin: {
customerId: 'user_123',
feature: 'chat.send',
requested: { standard: true, premium: true, search: true },
idempotencyKey: 'chat-send-user-123-req-456',
},
request,
entitlementMode: 'apply',
modelTiers: {
standard: 'gpt-5.4-mini',
premium: 'gpt-5.4',
},
toolEntitlements: {
image_tool: 'image',
web_lookup: 'search',
},
invoke: async (requestUsed) => {
const response = await fetch('https://api.openai.com/v1/chat/completions', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
Authorization: `Bearer ${process.env.OPENAI_API_KEY}`,
},
body: JSON.stringify(requestUsed.body),
});
return response.json();
},
});
tracked.response;
tracked.begin;
tracked.end;
tracked.requestUsed;
tracked.effectiveUsage;
tracked.allowed;runOpenRouterWithUsageTap, runLLMAsAServiceWithUsageTap, runAnthropicWithUsageTap, and runGeminiWithUsageTap follow the same pattern.
entitlementMode defaults to 'off'.
'off': track the call, but do not change the provider request.'apply': clone the provider request and apply UsageTap allowances before invoking the vendor.
When entitlementMode: 'apply' is enabled, helpers can:
- Swap to
modelTiers.standardormodelTiers.premium. - Cap OpenAI, OpenRouter, or LLMAsAService
reasoning_effort. - Cap Gemini
thinkingConfig.thinkingBudget. If no Gemini thinking budget was present, the helper now sets one from the allowed reasoning level. - Remove built-in OpenAI-compatible
web_searchwhenallowed.search === false. - Remove named tools according to
toolEntitlementsfor OpenAI, OpenRouter, LLMAsAService, Anthropic, and Gemini function declarations.
Notes:
- Mutations happen on a cloned request. The original
requestobject is left unchanged. - Built-in tool gating is intentionally narrow today. Only OpenAI
web_searchis handled automatically; other built-ins must be mapped through your own tool policy. - Applying Gemini reasoning limits can introduce a
thinkingConfigblock even when the original request omitted one.
Use withUsageTapCall when you want begin/invoke/end tracking without a provider-specific helper.
import { createUsageTapClient, withUsageTapCall } from 'promptopskit/usagetap';
const usageTap = createUsageTapClient({ apiKey: process.env.USAGETAP_API_KEY! });
const tracked = await withUsageTapCall(usageTap, {
begin: {
customerId: 'user_123',
feature: 'embeddings.index',
},
invoke: async ({ setUsage, signal }) => {
const response = await fetch('https://api.openai.com/v1/embeddings', {
method: 'POST',
signal,
headers: {
'Content-Type': 'application/json',
Authorization: `Bearer ${process.env.OPENAI_API_KEY}`,
},
body: JSON.stringify({
model: 'text-embedding-3-large',
input: 'PromptOpsKit',
}),
}).then((result) => result.json());
setUsage({
modelUsed: 'text-embedding-3-large',
inputTokens: 6,
});
return response;
},
});withUsageTapCall also accepts an invoke result shaped like { result, usage } if you prefer to return the usage payload directly.
If the vendor call throws and UsageTap call_end also fails, the original vendor error is rethrown and the UsageTap failure is attached as error.cause when possible.
The provider runners use public extractor helpers that you can call yourself:
extractOpenAIUsage(response, meta)extractAnthropicUsage(response, meta)extractGeminiUsage(response, meta)
They map common token fields into the UsageTap call_end payload shape:
- OpenAI, OpenRouter, and LLMAsAService:
usage.prompt_tokens,completion_tokens, cached prompt tokens, reasoning tokens - Anthropic:
usage.input_tokens,output_tokens,cache_read_input_tokens - Gemini:
usageMetadata.promptTokenCount,candidatesTokenCount,cachedContentTokenCount,thoughtsTokenCount
Main exports:
createUsageTapClientbeginUsageTapCallendUsageTapCallwithUsageTapCallapplyUsageTapEntitlementsrunOpenAIWithUsageTaprunOpenRouterWithUsageTaprunLLMAsAServiceWithUsageTaprunAnthropicWithUsageTaprunGeminiWithUsageTapextractOpenAIUsageextractAnthropicUsageextractGeminiUsagedefaultUsageTapErrorMapper
Relevant types:
UsageTapBeginRequestUsageTapBeginResponseUsageTapEndUsageUsageTapCallOptionsUsageTapProviderRunOptionsUsageTapEntitlementOptionsUsageTapAllowed
For the rest of the PromptOpsKit provider model, see Providers.