@hiper2d/ai-agents 0.2.0 → 0.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +22 -0
- package/dist/index.d.mts +154 -1
- package/dist/index.d.ts +154 -1
- package/dist/index.js +198 -18
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +188 -18
- package/dist/index.mjs.map +1 -1
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -74,6 +74,28 @@ Transcribe (`VOICE_MODEL_CONSTANTS`, prices in `VOICE_MODEL_PRICING`). The `voic
|
|
|
74
74
|
direction works for both providers: OpenAI takes it as instructions, Gemini gets it folded
|
|
75
75
|
into the prompt ("Say gravely: …").
|
|
76
76
|
|
|
77
|
+
### Budget control
|
|
78
|
+
|
|
79
|
+
Per-subject spend caps (a user, a tenant, a job) over UTC day and month windows. Pure and
|
|
80
|
+
storage-agnostic: period keys, an O(1) rolling ledger that is overwritten when the period
|
|
81
|
+
rolls, a verdict function, and `BudgetExceededError` carrying the verdict (`resetsAt`,
|
|
82
|
+
`remainingUSD`) so the host can render "come back at …" instead of a provider failure.
|
|
83
|
+
|
|
84
|
+
```ts
|
|
85
|
+
import { BudgetController, InMemorySpendStore, BudgetExceededError } from '@hiper2d/ai-agents';
|
|
86
|
+
|
|
87
|
+
const budget = new BudgetController(new InMemorySpendStore(), {
|
|
88
|
+
limits: [{ window: 'day', limitUSD: 5 }, { window: 'month', limitUSD: 20 }],
|
|
89
|
+
});
|
|
90
|
+
await budget.assertWithinBudget(userId); // cheap pre-call guard, throws BudgetExceededError
|
|
91
|
+
const { costUSD } = await agent.ask(...);
|
|
92
|
+
await budget.record(userId, costUSD); // re-checks inside the store's atomic update
|
|
93
|
+
```
|
|
94
|
+
|
|
95
|
+
Hosts that bill inside their own database transaction skip the controller and call the
|
|
96
|
+
pure pieces there: `ledgerSpend` → `evaluateBudget` → `applySpend`. Implement `SpendStore`
|
|
97
|
+
to back the controller with Redis, Postgres, Firestore, etc.
|
|
98
|
+
|
|
77
99
|
### Logging
|
|
78
100
|
|
|
79
101
|
The library logs through an injectable sink — `setLlmLogger(fn)` — so a host app can route
|
package/dist/index.d.mts
CHANGED
|
@@ -1019,6 +1019,146 @@ declare const GEMINI_AUDIO_TOKENS_PER_SECOND = 25;
|
|
|
1019
1019
|
*/
|
|
1020
1020
|
declare function transcribeWithGemini(audioBuffer: ArrayBuffer, apiKey: string, options?: GoogleSttOptions): Promise<GoogleSttResult>;
|
|
1021
1021
|
|
|
1022
|
+
/**
|
|
1023
|
+
* Budget control for agent spend — pure and storage-agnostic.
|
|
1024
|
+
*
|
|
1025
|
+
* The problem this solves: an app that runs LLM/voice/image calls on its own keys needs
|
|
1026
|
+
* to bound what one subject (a user, a tenant, a job) can spend per UTC day or month, and
|
|
1027
|
+
* needs the check to be cheap enough to run before EVERY call. The pieces:
|
|
1028
|
+
*
|
|
1029
|
+
* - `periodKey` / `periodEnd`: UTC window keys (`YYYY-MM-DD`, `YYYY-MM`) and reset times.
|
|
1030
|
+
* - `SpendLedger` + `applySpend`: an O(1) rolling ledger for one window. It is overwritten,
|
|
1031
|
+
* not appended, when the period changes, so the persisted record never grows; history
|
|
1032
|
+
* belongs in your per-request stats, not here.
|
|
1033
|
+
* - `evaluateBudget`: the verdict. Pure, no I/O, no throwing.
|
|
1034
|
+
* - `BudgetExceededError`: what to throw when a verdict refuses, carrying the verdict so
|
|
1035
|
+
* the caller can render "come back at <resetsAt>" instead of treating it as a provider
|
|
1036
|
+
* failure.
|
|
1037
|
+
* - `BudgetController` over a `SpendStore`: batteries-included wrapper for projects that
|
|
1038
|
+
* do not need to fold the ledger into their own database transaction. Apps that do
|
|
1039
|
+
* (the werewolf app charges the user, updates the game and writes a stats row in one
|
|
1040
|
+
* Firestore transaction) use the pure functions directly inside that transaction.
|
|
1041
|
+
*
|
|
1042
|
+
* Amounts are USD, rounded to 6 decimals like the rest of the cost accounting.
|
|
1043
|
+
*/
|
|
1044
|
+
type SpendWindow = 'day' | 'month';
|
|
1045
|
+
/** Where the money went; free-form beyond the common kinds so apps can add their own. */
|
|
1046
|
+
type SpendKind = 'llm' | 'image' | 'tts' | 'stt' | (string & {});
|
|
1047
|
+
/** UTC key for the window containing `timestamp`: `2026-09-11` for a day, `2026-09` for a month. */
|
|
1048
|
+
declare function periodKey(timestamp: number, window: SpendWindow): string;
|
|
1049
|
+
/** First millisecond of the window after the one containing `timestamp` — i.e. when the budget resets. */
|
|
1050
|
+
declare function periodEnd(timestamp: number, window: SpendWindow): number;
|
|
1051
|
+
/**
|
|
1052
|
+
* Spend recorded for ONE window. `buckets` split the total by whatever the app cares
|
|
1053
|
+
* about (tier, model family, feature); the total is authoritative, buckets are a breakdown.
|
|
1054
|
+
*/
|
|
1055
|
+
interface SpendLedger {
|
|
1056
|
+
period: string;
|
|
1057
|
+
totalUSD: number;
|
|
1058
|
+
buckets: Record<string, number>;
|
|
1059
|
+
}
|
|
1060
|
+
interface SpendInput {
|
|
1061
|
+
window: SpendWindow;
|
|
1062
|
+
amountUSD: number;
|
|
1063
|
+
/** Bucket to add the amount to, on top of the total. Omit to update the total only. */
|
|
1064
|
+
bucket?: string;
|
|
1065
|
+
timestamp?: number;
|
|
1066
|
+
}
|
|
1067
|
+
/**
|
|
1068
|
+
* Pure reducer: add `amountUSD` to the ledger for the window containing `timestamp`.
|
|
1069
|
+
* A ledger from an earlier period is discarded and a fresh one started (overwrite
|
|
1070
|
+
* semantics), so the persisted record stays O(1). Non-positive amounts return the
|
|
1071
|
+
* ledger normalized to the current period without recording anything.
|
|
1072
|
+
*/
|
|
1073
|
+
declare function applySpend(ledger: Partial<SpendLedger> | null | undefined, input: SpendInput): SpendLedger;
|
|
1074
|
+
/**
|
|
1075
|
+
* Spend already recorded in the window containing `timestamp` — the total, or one bucket.
|
|
1076
|
+
* A missing ledger or one from another period counts as 0.
|
|
1077
|
+
*/
|
|
1078
|
+
declare function ledgerSpend(ledger: Partial<SpendLedger> | null | undefined, window: SpendWindow, timestamp?: number, bucket?: string): number;
|
|
1079
|
+
interface BudgetLimit {
|
|
1080
|
+
window: SpendWindow;
|
|
1081
|
+
/** USD ceiling for the window. `Infinity` (or any non-finite) means unlimited. */
|
|
1082
|
+
limitUSD: number;
|
|
1083
|
+
/** Which bucket the limit applies to; informational for the verdict, the caller passes the matching spend. */
|
|
1084
|
+
bucket?: string;
|
|
1085
|
+
}
|
|
1086
|
+
interface BudgetVerdict {
|
|
1087
|
+
allowed: boolean;
|
|
1088
|
+
window: SpendWindow;
|
|
1089
|
+
bucket?: string;
|
|
1090
|
+
limitUSD: number;
|
|
1091
|
+
spentUSD: number;
|
|
1092
|
+
remainingUSD: number;
|
|
1093
|
+
/** Epoch ms when the window rolls over and the spend counts from zero again. */
|
|
1094
|
+
resetsAt: number;
|
|
1095
|
+
}
|
|
1096
|
+
/**
|
|
1097
|
+
* The verdict for one limit given the spend already recorded in its window. Refuses when
|
|
1098
|
+
* spent >= limit (a subject exactly at the cap gets no more calls). Pure: no I/O, no throw.
|
|
1099
|
+
*/
|
|
1100
|
+
declare function evaluateBudget(spentUSD: number, limit: BudgetLimit, timestamp?: number): BudgetVerdict;
|
|
1101
|
+
/** The first refusing verdict, or undefined when every window still has room. */
|
|
1102
|
+
declare function firstRefusal(verdicts: BudgetVerdict[]): BudgetVerdict | undefined;
|
|
1103
|
+
/**
|
|
1104
|
+
* Thrown when a budget refuses a call. Not a `ModelError`: nothing was sent to a provider
|
|
1105
|
+
* and nothing is retryable until `verdict.resetsAt`. Apps typically subclass it to attach
|
|
1106
|
+
* user-facing copy, or map it to their own error at the boundary.
|
|
1107
|
+
*/
|
|
1108
|
+
declare class BudgetExceededError extends Error {
|
|
1109
|
+
readonly verdict: BudgetVerdict;
|
|
1110
|
+
readonly subject?: string;
|
|
1111
|
+
constructor(verdict: BudgetVerdict, subject?: string, message?: string);
|
|
1112
|
+
static describe(v: BudgetVerdict): string;
|
|
1113
|
+
}
|
|
1114
|
+
declare function isBudgetExceededError(err: unknown): err is BudgetExceededError;
|
|
1115
|
+
type SpendLedgers = Partial<Record<SpendWindow, SpendLedger>>;
|
|
1116
|
+
/**
|
|
1117
|
+
* Persistence for per-subject ledgers. `update` must apply `fn` to the current ledgers
|
|
1118
|
+
* and persist the result atomically for that subject (a transaction, a lock, a
|
|
1119
|
+
* single-threaded map — whatever the backend offers). If `fn` throws, nothing is written.
|
|
1120
|
+
*/
|
|
1121
|
+
interface SpendStore {
|
|
1122
|
+
read(subject: string): Promise<SpendLedgers>;
|
|
1123
|
+
update(subject: string, fn: (current: SpendLedgers) => SpendLedgers): Promise<SpendLedgers>;
|
|
1124
|
+
}
|
|
1125
|
+
/** Process-local store: fine for tests, CLIs and single-instance jobs. */
|
|
1126
|
+
declare class InMemorySpendStore implements SpendStore {
|
|
1127
|
+
private readonly ledgers;
|
|
1128
|
+
read(subject: string): Promise<SpendLedgers>;
|
|
1129
|
+
update(subject: string, fn: (current: SpendLedgers) => SpendLedgers): Promise<SpendLedgers>;
|
|
1130
|
+
}
|
|
1131
|
+
interface BudgetControllerOptions {
|
|
1132
|
+
limits: BudgetLimit[];
|
|
1133
|
+
/** Bucket every recorded spend is also added to (e.g. the subject's tier). */
|
|
1134
|
+
bucket?: string;
|
|
1135
|
+
clock?: () => number;
|
|
1136
|
+
}
|
|
1137
|
+
/**
|
|
1138
|
+
* Check-before, record-after budget control for one set of limits.
|
|
1139
|
+
*
|
|
1140
|
+
* `assertWithinBudget` is the cheap pre-call guard; `record` re-evaluates inside the
|
|
1141
|
+
* store's atomic update so concurrent callers cannot all slip past a nearly-exhausted
|
|
1142
|
+
* budget. The overrun that remains is bounded by the calls already in flight when the
|
|
1143
|
+
* cap is crossed — state that bound, do not claim the cap is exact.
|
|
1144
|
+
*/
|
|
1145
|
+
declare class BudgetController {
|
|
1146
|
+
private readonly store;
|
|
1147
|
+
private readonly limits;
|
|
1148
|
+
private readonly bucket?;
|
|
1149
|
+
private readonly clock;
|
|
1150
|
+
constructor(store: SpendStore, options: BudgetControllerOptions);
|
|
1151
|
+
private verdicts;
|
|
1152
|
+
check(subject: string): Promise<BudgetVerdict[]>;
|
|
1153
|
+
assertWithinBudget(subject: string): Promise<void>;
|
|
1154
|
+
/**
|
|
1155
|
+
* Record `amountUSD` against every limited window. Re-checks the limits on the
|
|
1156
|
+
* ledgers as they are at write time and throws `BudgetExceededError` (writing
|
|
1157
|
+
* nothing) if any window is already exhausted.
|
|
1158
|
+
*/
|
|
1159
|
+
record(subject: string, amountUSD: number): Promise<SpendLedgers>;
|
|
1160
|
+
}
|
|
1161
|
+
|
|
1022
1162
|
declare abstract class AbstractAgent {
|
|
1023
1163
|
name: string;
|
|
1024
1164
|
gameId?: string;
|
|
@@ -1092,8 +1232,20 @@ declare class AgentFactory {
|
|
|
1092
1232
|
private static validateLlmTypeAndGet;
|
|
1093
1233
|
}
|
|
1094
1234
|
|
|
1235
|
+
type CacheTtl = '5m' | '1h';
|
|
1095
1236
|
declare class ClaudeAgent extends AbstractAgent {
|
|
1096
1237
|
private readonly client;
|
|
1238
|
+
/**
|
|
1239
|
+
* TTL for every breakpoint this agent places (system tiers and the message anchor).
|
|
1240
|
+
* Anthropic bills a 5m write at 1.25x input, a 1h write at 2x, reads at 0.1x, and a read
|
|
1241
|
+
* refreshes the timer on either TTL. Default '1h' because the main consumer runs at human
|
|
1242
|
+
* pace: consecutive calls for one agent measured 12-78 minutes apart, so 5m entries
|
|
1243
|
+
* expired before they were ever read (0-16% hit rate over 30 days, hits only on gaps
|
|
1244
|
+
* under five minutes). Set '5m' for continuous traffic where every call lands inside the
|
|
1245
|
+
* window; there the cheaper write wins. One knob for all breakpoints on purpose: Anthropic
|
|
1246
|
+
* requires 1h entries to precede 5m ones, and a single TTL keeps that trivially true.
|
|
1247
|
+
*/
|
|
1248
|
+
cacheTtl: CacheTtl;
|
|
1097
1249
|
private get defaultParams();
|
|
1098
1250
|
private readonly logTemplates;
|
|
1099
1251
|
private readonly errorMessages;
|
|
@@ -1150,6 +1302,7 @@ declare class ClaudeAgent extends AbstractAgent {
|
|
|
1150
1302
|
|
|
1151
1303
|
declare class Gpt5Agent extends AbstractAgent {
|
|
1152
1304
|
private readonly client;
|
|
1305
|
+
private readonly promptCacheKey;
|
|
1153
1306
|
private readonly logTemplates;
|
|
1154
1307
|
private readonly errorMessages;
|
|
1155
1308
|
constructor(name: string, instruction: string, model: string, apiKey: string, temperature: number, enableThinking?: boolean, agentLoggingConfig?: AgentLoggingConfig);
|
|
@@ -1420,4 +1573,4 @@ declare class MiniMaxAgent extends AbstractAgent {
|
|
|
1420
1573
|
doAskText(messages: AIMessage[]): Promise<[string, string, TokenUsage$1?, string?]>;
|
|
1421
1574
|
}
|
|
1422
1575
|
|
|
1423
|
-
export { type AIMessage, ANTHROPIC_REASONING_EFFORTS, API_KEY_CONSTANTS, AbstractAgent, type AgentActivityData, AgentFactory, type AgentLoggingConfig, type AnthropicReasoningEffort, type AnthropicTokenUsage, type ApiKeyMap, BotResponseError, CACHE_TIER_MARKER, ClaudeAgent, type CostCalculationOptions, DEEPSEEK_PEAK_SCHEDULE, DEEPSEEK_REASONING_EFFORTS, DEFAULT_LOGGING_CONFIG, DEFAULT_MAX_OUTPUT_TOKENS, type DeepSeekReasoningEffort, type DeepSeekTokenUsage, DeepSeekV2Agent, FUGU_REASONING_EFFORTS, FuguAgent, type FuguReasoningEffort, GEMINI_AUDIO_TOKENS_PER_SECOND, GEMINI_REASONING_EFFORTS, GLM_REASONING_EFFORTS, type GeminiReasoningEffort, GlmAgent, type GlmReasoningEffort, GoogleAgent, type GoogleSttOptions, type GoogleSttResult, type GoogleTokenUsage, type GoogleTtsAudioOptions, type GoogleTtsResult, GoogleVoiceAgent, Gpt5Agent, GrokAgent, type GrokTokenUsage, type JsonSchemaOptions, KimiAgent, type KimiTokenUsage, type LLMModel, LLM_CONSTANTS, type LlmLogger, type LoggingConfig, MESSAGE_ROLE, MODEL_PRICING, MiniMaxAgent, MistralAgent, type MistralTokenUsage, type Modality, ModelAuthenticationError, type ModelConfig, ModelError, ModelInvalidResponseError, ModelOverloadError, type ModelPricing, ModelQuotaExceededError, ModelRateLimitError, ModelRefusalError, type ModelTag, ModelUnavailableError, OPENAI_REASONING_EFFORTS, type OpenAIReasoningEffort, type OpenAITokenUsage, type OpenAiSttOptions, type OpenAiSttResult, type OpenAiTtsAudioOptions, type OpenAiTtsVoice, OpenAiVoiceAgent, type PeakPricing, type PricingUnit, type ProviderSchema, type TokenUsage as ProviderTokenUsage, type ProviderType, QwenAgent, REASONING_EFFORT_SCALE, type ReasoningEffort, SUPPORTED_VOICE_PROVIDERS, type SpeechRequest, type SpeechResult, type SpeechUsage, SupportedAiKeyNames, SupportedAiModels, type TokenPairUsage, type TokenUsage$1 as TokenUsage, type TranscriptionRequest, type TranscriptionResult, type TranscriptionUsage, VOICE_MODEL_CONSTANTS, VOICE_MODEL_PRICING, VOICE_PROVIDER_API_KEY, type VoiceAgent, VoiceAgentFactory, type VoiceModelPricing, type VoiceProvider, ZodSchemaConverter, buildGoogleTtsPrompt, calculateAnthropicCost, calculateCost, calculateDeepSeekCost, calculateGeminiSttCost, calculateGeminiTtsCost, calculateGoogleCost, calculateGrokCost, calculateKimiCost, calculateMistralCost, calculateModelCost, calculateOpenAICost, calculateOpenAiSttCost, calculateOpenAiTtsCost, clampReasoningEffort, cleanResponse, createCatalog, createVoiceAgent, extractAnthropicTokenUsage, extractTokenUsageFromResponse$2 as extractAnthropicTokenUsageFromResponse, extractDeepSeekTokenUsage, extractTokenUsageFromResponse$5 as extractDeepSeekTokenUsageFromResponse, extractFirstJsonObject, extractGoogleTokenUsage, extractTokenUsageFromResponse$1 as extractGoogleTokenUsageFromResponse, extractGrokTokenUsage, extractTokenUsageFromResponse$3 as extractGrokTokenUsageFromResponse, extractKimiTokenUsage, extractTokenUsageFromResponse$4 as extractKimiTokenUsageFromResponse, extractMistralTokenUsage, extractTokenUsageFromResponse as extractMistralTokenUsageFromResponse, extractOpenAITokenUsage, extractTokenUsageFromResponse$6 as extractOpenAITokenUsageFromResponse, extractTokenUsage, extractUsageAndCalculateCost, generateGoogleTtsAudio, generateOpenAiTtsAudio, generateSchemaInstructions, getModelConfigByApiName, getModelDisplayName, getModelProviderName, getModelTags, getProviderSignatureFields, isHybridThinkingModel, isInPeakWindow, isPeakBilling, isWeekendAt, logger, mergeThinking, modelHasTag, modelIsFast, needsPromptBasedSchema, parseAndValidateLlmJson, pcmToWav, safeValidateResponse, setLlmLogger, stableHashHex, stripInlineThinking, supportsNativeJsonSchema, toAnthropicEffort, toDeepSeekEffort, toFuguEffort, toGeminiEffort, toGlmEffort, toOpenAIEffort, transcribeWithGemini, transcribeWithOpenAi, validateResponse };
|
|
1576
|
+
export { type AIMessage, ANTHROPIC_REASONING_EFFORTS, API_KEY_CONSTANTS, AbstractAgent, type AgentActivityData, AgentFactory, type AgentLoggingConfig, type AnthropicReasoningEffort, type AnthropicTokenUsage, type ApiKeyMap, BotResponseError, BudgetController, type BudgetControllerOptions, BudgetExceededError, type BudgetLimit, type BudgetVerdict, CACHE_TIER_MARKER, ClaudeAgent, type CostCalculationOptions, DEEPSEEK_PEAK_SCHEDULE, DEEPSEEK_REASONING_EFFORTS, DEFAULT_LOGGING_CONFIG, DEFAULT_MAX_OUTPUT_TOKENS, type DeepSeekReasoningEffort, type DeepSeekTokenUsage, DeepSeekV2Agent, FUGU_REASONING_EFFORTS, FuguAgent, type FuguReasoningEffort, GEMINI_AUDIO_TOKENS_PER_SECOND, GEMINI_REASONING_EFFORTS, GLM_REASONING_EFFORTS, type GeminiReasoningEffort, GlmAgent, type GlmReasoningEffort, GoogleAgent, type GoogleSttOptions, type GoogleSttResult, type GoogleTokenUsage, type GoogleTtsAudioOptions, type GoogleTtsResult, GoogleVoiceAgent, Gpt5Agent, GrokAgent, type GrokTokenUsage, InMemorySpendStore, type JsonSchemaOptions, KimiAgent, type KimiTokenUsage, type LLMModel, LLM_CONSTANTS, type LlmLogger, type LoggingConfig, MESSAGE_ROLE, MODEL_PRICING, MiniMaxAgent, MistralAgent, type MistralTokenUsage, type Modality, ModelAuthenticationError, type ModelConfig, ModelError, ModelInvalidResponseError, ModelOverloadError, type ModelPricing, ModelQuotaExceededError, ModelRateLimitError, ModelRefusalError, type ModelTag, ModelUnavailableError, OPENAI_REASONING_EFFORTS, type OpenAIReasoningEffort, type OpenAITokenUsage, type OpenAiSttOptions, type OpenAiSttResult, type OpenAiTtsAudioOptions, type OpenAiTtsVoice, OpenAiVoiceAgent, type PeakPricing, type PricingUnit, type ProviderSchema, type TokenUsage as ProviderTokenUsage, type ProviderType, QwenAgent, REASONING_EFFORT_SCALE, type ReasoningEffort, SUPPORTED_VOICE_PROVIDERS, type SpeechRequest, type SpeechResult, type SpeechUsage, type SpendInput, type SpendKind, type SpendLedger, type SpendLedgers, type SpendStore, type SpendWindow, SupportedAiKeyNames, SupportedAiModels, type TokenPairUsage, type TokenUsage$1 as TokenUsage, type TranscriptionRequest, type TranscriptionResult, type TranscriptionUsage, VOICE_MODEL_CONSTANTS, VOICE_MODEL_PRICING, VOICE_PROVIDER_API_KEY, type VoiceAgent, VoiceAgentFactory, type VoiceModelPricing, type VoiceProvider, ZodSchemaConverter, applySpend, buildGoogleTtsPrompt, calculateAnthropicCost, calculateCost, calculateDeepSeekCost, calculateGeminiSttCost, calculateGeminiTtsCost, calculateGoogleCost, calculateGrokCost, calculateKimiCost, calculateMistralCost, calculateModelCost, calculateOpenAICost, calculateOpenAiSttCost, calculateOpenAiTtsCost, clampReasoningEffort, cleanResponse, createCatalog, createVoiceAgent, evaluateBudget, extractAnthropicTokenUsage, extractTokenUsageFromResponse$2 as extractAnthropicTokenUsageFromResponse, extractDeepSeekTokenUsage, extractTokenUsageFromResponse$5 as extractDeepSeekTokenUsageFromResponse, extractFirstJsonObject, extractGoogleTokenUsage, extractTokenUsageFromResponse$1 as extractGoogleTokenUsageFromResponse, extractGrokTokenUsage, extractTokenUsageFromResponse$3 as extractGrokTokenUsageFromResponse, extractKimiTokenUsage, extractTokenUsageFromResponse$4 as extractKimiTokenUsageFromResponse, extractMistralTokenUsage, extractTokenUsageFromResponse as extractMistralTokenUsageFromResponse, extractOpenAITokenUsage, extractTokenUsageFromResponse$6 as extractOpenAITokenUsageFromResponse, extractTokenUsage, extractUsageAndCalculateCost, firstRefusal, generateGoogleTtsAudio, generateOpenAiTtsAudio, generateSchemaInstructions, getModelConfigByApiName, getModelDisplayName, getModelProviderName, getModelTags, getProviderSignatureFields, isBudgetExceededError, isHybridThinkingModel, isInPeakWindow, isPeakBilling, isWeekendAt, ledgerSpend, logger, mergeThinking, modelHasTag, modelIsFast, needsPromptBasedSchema, parseAndValidateLlmJson, pcmToWav, periodEnd, periodKey, safeValidateResponse, setLlmLogger, stableHashHex, stripInlineThinking, supportsNativeJsonSchema, toAnthropicEffort, toDeepSeekEffort, toFuguEffort, toGeminiEffort, toGlmEffort, toOpenAIEffort, transcribeWithGemini, transcribeWithOpenAi, validateResponse };
|
package/dist/index.d.ts
CHANGED
|
@@ -1019,6 +1019,146 @@ declare const GEMINI_AUDIO_TOKENS_PER_SECOND = 25;
|
|
|
1019
1019
|
*/
|
|
1020
1020
|
declare function transcribeWithGemini(audioBuffer: ArrayBuffer, apiKey: string, options?: GoogleSttOptions): Promise<GoogleSttResult>;
|
|
1021
1021
|
|
|
1022
|
+
/**
|
|
1023
|
+
* Budget control for agent spend — pure and storage-agnostic.
|
|
1024
|
+
*
|
|
1025
|
+
* The problem this solves: an app that runs LLM/voice/image calls on its own keys needs
|
|
1026
|
+
* to bound what one subject (a user, a tenant, a job) can spend per UTC day or month, and
|
|
1027
|
+
* needs the check to be cheap enough to run before EVERY call. The pieces:
|
|
1028
|
+
*
|
|
1029
|
+
* - `periodKey` / `periodEnd`: UTC window keys (`YYYY-MM-DD`, `YYYY-MM`) and reset times.
|
|
1030
|
+
* - `SpendLedger` + `applySpend`: an O(1) rolling ledger for one window. It is overwritten,
|
|
1031
|
+
* not appended, when the period changes, so the persisted record never grows; history
|
|
1032
|
+
* belongs in your per-request stats, not here.
|
|
1033
|
+
* - `evaluateBudget`: the verdict. Pure, no I/O, no throwing.
|
|
1034
|
+
* - `BudgetExceededError`: what to throw when a verdict refuses, carrying the verdict so
|
|
1035
|
+
* the caller can render "come back at <resetsAt>" instead of treating it as a provider
|
|
1036
|
+
* failure.
|
|
1037
|
+
* - `BudgetController` over a `SpendStore`: batteries-included wrapper for projects that
|
|
1038
|
+
* do not need to fold the ledger into their own database transaction. Apps that do
|
|
1039
|
+
* (the werewolf app charges the user, updates the game and writes a stats row in one
|
|
1040
|
+
* Firestore transaction) use the pure functions directly inside that transaction.
|
|
1041
|
+
*
|
|
1042
|
+
* Amounts are USD, rounded to 6 decimals like the rest of the cost accounting.
|
|
1043
|
+
*/
|
|
1044
|
+
type SpendWindow = 'day' | 'month';
|
|
1045
|
+
/** Where the money went; free-form beyond the common kinds so apps can add their own. */
|
|
1046
|
+
type SpendKind = 'llm' | 'image' | 'tts' | 'stt' | (string & {});
|
|
1047
|
+
/** UTC key for the window containing `timestamp`: `2026-09-11` for a day, `2026-09` for a month. */
|
|
1048
|
+
declare function periodKey(timestamp: number, window: SpendWindow): string;
|
|
1049
|
+
/** First millisecond of the window after the one containing `timestamp` — i.e. when the budget resets. */
|
|
1050
|
+
declare function periodEnd(timestamp: number, window: SpendWindow): number;
|
|
1051
|
+
/**
|
|
1052
|
+
* Spend recorded for ONE window. `buckets` split the total by whatever the app cares
|
|
1053
|
+
* about (tier, model family, feature); the total is authoritative, buckets are a breakdown.
|
|
1054
|
+
*/
|
|
1055
|
+
interface SpendLedger {
|
|
1056
|
+
period: string;
|
|
1057
|
+
totalUSD: number;
|
|
1058
|
+
buckets: Record<string, number>;
|
|
1059
|
+
}
|
|
1060
|
+
interface SpendInput {
|
|
1061
|
+
window: SpendWindow;
|
|
1062
|
+
amountUSD: number;
|
|
1063
|
+
/** Bucket to add the amount to, on top of the total. Omit to update the total only. */
|
|
1064
|
+
bucket?: string;
|
|
1065
|
+
timestamp?: number;
|
|
1066
|
+
}
|
|
1067
|
+
/**
|
|
1068
|
+
* Pure reducer: add `amountUSD` to the ledger for the window containing `timestamp`.
|
|
1069
|
+
* A ledger from an earlier period is discarded and a fresh one started (overwrite
|
|
1070
|
+
* semantics), so the persisted record stays O(1). Non-positive amounts return the
|
|
1071
|
+
* ledger normalized to the current period without recording anything.
|
|
1072
|
+
*/
|
|
1073
|
+
declare function applySpend(ledger: Partial<SpendLedger> | null | undefined, input: SpendInput): SpendLedger;
|
|
1074
|
+
/**
|
|
1075
|
+
* Spend already recorded in the window containing `timestamp` — the total, or one bucket.
|
|
1076
|
+
* A missing ledger or one from another period counts as 0.
|
|
1077
|
+
*/
|
|
1078
|
+
declare function ledgerSpend(ledger: Partial<SpendLedger> | null | undefined, window: SpendWindow, timestamp?: number, bucket?: string): number;
|
|
1079
|
+
interface BudgetLimit {
|
|
1080
|
+
window: SpendWindow;
|
|
1081
|
+
/** USD ceiling for the window. `Infinity` (or any non-finite) means unlimited. */
|
|
1082
|
+
limitUSD: number;
|
|
1083
|
+
/** Which bucket the limit applies to; informational for the verdict, the caller passes the matching spend. */
|
|
1084
|
+
bucket?: string;
|
|
1085
|
+
}
|
|
1086
|
+
interface BudgetVerdict {
|
|
1087
|
+
allowed: boolean;
|
|
1088
|
+
window: SpendWindow;
|
|
1089
|
+
bucket?: string;
|
|
1090
|
+
limitUSD: number;
|
|
1091
|
+
spentUSD: number;
|
|
1092
|
+
remainingUSD: number;
|
|
1093
|
+
/** Epoch ms when the window rolls over and the spend counts from zero again. */
|
|
1094
|
+
resetsAt: number;
|
|
1095
|
+
}
|
|
1096
|
+
/**
|
|
1097
|
+
* The verdict for one limit given the spend already recorded in its window. Refuses when
|
|
1098
|
+
* spent >= limit (a subject exactly at the cap gets no more calls). Pure: no I/O, no throw.
|
|
1099
|
+
*/
|
|
1100
|
+
declare function evaluateBudget(spentUSD: number, limit: BudgetLimit, timestamp?: number): BudgetVerdict;
|
|
1101
|
+
/** The first refusing verdict, or undefined when every window still has room. */
|
|
1102
|
+
declare function firstRefusal(verdicts: BudgetVerdict[]): BudgetVerdict | undefined;
|
|
1103
|
+
/**
|
|
1104
|
+
* Thrown when a budget refuses a call. Not a `ModelError`: nothing was sent to a provider
|
|
1105
|
+
* and nothing is retryable until `verdict.resetsAt`. Apps typically subclass it to attach
|
|
1106
|
+
* user-facing copy, or map it to their own error at the boundary.
|
|
1107
|
+
*/
|
|
1108
|
+
declare class BudgetExceededError extends Error {
|
|
1109
|
+
readonly verdict: BudgetVerdict;
|
|
1110
|
+
readonly subject?: string;
|
|
1111
|
+
constructor(verdict: BudgetVerdict, subject?: string, message?: string);
|
|
1112
|
+
static describe(v: BudgetVerdict): string;
|
|
1113
|
+
}
|
|
1114
|
+
declare function isBudgetExceededError(err: unknown): err is BudgetExceededError;
|
|
1115
|
+
type SpendLedgers = Partial<Record<SpendWindow, SpendLedger>>;
|
|
1116
|
+
/**
|
|
1117
|
+
* Persistence for per-subject ledgers. `update` must apply `fn` to the current ledgers
|
|
1118
|
+
* and persist the result atomically for that subject (a transaction, a lock, a
|
|
1119
|
+
* single-threaded map — whatever the backend offers). If `fn` throws, nothing is written.
|
|
1120
|
+
*/
|
|
1121
|
+
interface SpendStore {
|
|
1122
|
+
read(subject: string): Promise<SpendLedgers>;
|
|
1123
|
+
update(subject: string, fn: (current: SpendLedgers) => SpendLedgers): Promise<SpendLedgers>;
|
|
1124
|
+
}
|
|
1125
|
+
/** Process-local store: fine for tests, CLIs and single-instance jobs. */
|
|
1126
|
+
declare class InMemorySpendStore implements SpendStore {
|
|
1127
|
+
private readonly ledgers;
|
|
1128
|
+
read(subject: string): Promise<SpendLedgers>;
|
|
1129
|
+
update(subject: string, fn: (current: SpendLedgers) => SpendLedgers): Promise<SpendLedgers>;
|
|
1130
|
+
}
|
|
1131
|
+
interface BudgetControllerOptions {
|
|
1132
|
+
limits: BudgetLimit[];
|
|
1133
|
+
/** Bucket every recorded spend is also added to (e.g. the subject's tier). */
|
|
1134
|
+
bucket?: string;
|
|
1135
|
+
clock?: () => number;
|
|
1136
|
+
}
|
|
1137
|
+
/**
|
|
1138
|
+
* Check-before, record-after budget control for one set of limits.
|
|
1139
|
+
*
|
|
1140
|
+
* `assertWithinBudget` is the cheap pre-call guard; `record` re-evaluates inside the
|
|
1141
|
+
* store's atomic update so concurrent callers cannot all slip past a nearly-exhausted
|
|
1142
|
+
* budget. The overrun that remains is bounded by the calls already in flight when the
|
|
1143
|
+
* cap is crossed — state that bound, do not claim the cap is exact.
|
|
1144
|
+
*/
|
|
1145
|
+
declare class BudgetController {
|
|
1146
|
+
private readonly store;
|
|
1147
|
+
private readonly limits;
|
|
1148
|
+
private readonly bucket?;
|
|
1149
|
+
private readonly clock;
|
|
1150
|
+
constructor(store: SpendStore, options: BudgetControllerOptions);
|
|
1151
|
+
private verdicts;
|
|
1152
|
+
check(subject: string): Promise<BudgetVerdict[]>;
|
|
1153
|
+
assertWithinBudget(subject: string): Promise<void>;
|
|
1154
|
+
/**
|
|
1155
|
+
* Record `amountUSD` against every limited window. Re-checks the limits on the
|
|
1156
|
+
* ledgers as they are at write time and throws `BudgetExceededError` (writing
|
|
1157
|
+
* nothing) if any window is already exhausted.
|
|
1158
|
+
*/
|
|
1159
|
+
record(subject: string, amountUSD: number): Promise<SpendLedgers>;
|
|
1160
|
+
}
|
|
1161
|
+
|
|
1022
1162
|
declare abstract class AbstractAgent {
|
|
1023
1163
|
name: string;
|
|
1024
1164
|
gameId?: string;
|
|
@@ -1092,8 +1232,20 @@ declare class AgentFactory {
|
|
|
1092
1232
|
private static validateLlmTypeAndGet;
|
|
1093
1233
|
}
|
|
1094
1234
|
|
|
1235
|
+
type CacheTtl = '5m' | '1h';
|
|
1095
1236
|
declare class ClaudeAgent extends AbstractAgent {
|
|
1096
1237
|
private readonly client;
|
|
1238
|
+
/**
|
|
1239
|
+
* TTL for every breakpoint this agent places (system tiers and the message anchor).
|
|
1240
|
+
* Anthropic bills a 5m write at 1.25x input, a 1h write at 2x, reads at 0.1x, and a read
|
|
1241
|
+
* refreshes the timer on either TTL. Default '1h' because the main consumer runs at human
|
|
1242
|
+
* pace: consecutive calls for one agent measured 12-78 minutes apart, so 5m entries
|
|
1243
|
+
* expired before they were ever read (0-16% hit rate over 30 days, hits only on gaps
|
|
1244
|
+
* under five minutes). Set '5m' for continuous traffic where every call lands inside the
|
|
1245
|
+
* window; there the cheaper write wins. One knob for all breakpoints on purpose: Anthropic
|
|
1246
|
+
* requires 1h entries to precede 5m ones, and a single TTL keeps that trivially true.
|
|
1247
|
+
*/
|
|
1248
|
+
cacheTtl: CacheTtl;
|
|
1097
1249
|
private get defaultParams();
|
|
1098
1250
|
private readonly logTemplates;
|
|
1099
1251
|
private readonly errorMessages;
|
|
@@ -1150,6 +1302,7 @@ declare class ClaudeAgent extends AbstractAgent {
|
|
|
1150
1302
|
|
|
1151
1303
|
declare class Gpt5Agent extends AbstractAgent {
|
|
1152
1304
|
private readonly client;
|
|
1305
|
+
private readonly promptCacheKey;
|
|
1153
1306
|
private readonly logTemplates;
|
|
1154
1307
|
private readonly errorMessages;
|
|
1155
1308
|
constructor(name: string, instruction: string, model: string, apiKey: string, temperature: number, enableThinking?: boolean, agentLoggingConfig?: AgentLoggingConfig);
|
|
@@ -1420,4 +1573,4 @@ declare class MiniMaxAgent extends AbstractAgent {
|
|
|
1420
1573
|
doAskText(messages: AIMessage[]): Promise<[string, string, TokenUsage$1?, string?]>;
|
|
1421
1574
|
}
|
|
1422
1575
|
|
|
1423
|
-
export { type AIMessage, ANTHROPIC_REASONING_EFFORTS, API_KEY_CONSTANTS, AbstractAgent, type AgentActivityData, AgentFactory, type AgentLoggingConfig, type AnthropicReasoningEffort, type AnthropicTokenUsage, type ApiKeyMap, BotResponseError, CACHE_TIER_MARKER, ClaudeAgent, type CostCalculationOptions, DEEPSEEK_PEAK_SCHEDULE, DEEPSEEK_REASONING_EFFORTS, DEFAULT_LOGGING_CONFIG, DEFAULT_MAX_OUTPUT_TOKENS, type DeepSeekReasoningEffort, type DeepSeekTokenUsage, DeepSeekV2Agent, FUGU_REASONING_EFFORTS, FuguAgent, type FuguReasoningEffort, GEMINI_AUDIO_TOKENS_PER_SECOND, GEMINI_REASONING_EFFORTS, GLM_REASONING_EFFORTS, type GeminiReasoningEffort, GlmAgent, type GlmReasoningEffort, GoogleAgent, type GoogleSttOptions, type GoogleSttResult, type GoogleTokenUsage, type GoogleTtsAudioOptions, type GoogleTtsResult, GoogleVoiceAgent, Gpt5Agent, GrokAgent, type GrokTokenUsage, type JsonSchemaOptions, KimiAgent, type KimiTokenUsage, type LLMModel, LLM_CONSTANTS, type LlmLogger, type LoggingConfig, MESSAGE_ROLE, MODEL_PRICING, MiniMaxAgent, MistralAgent, type MistralTokenUsage, type Modality, ModelAuthenticationError, type ModelConfig, ModelError, ModelInvalidResponseError, ModelOverloadError, type ModelPricing, ModelQuotaExceededError, ModelRateLimitError, ModelRefusalError, type ModelTag, ModelUnavailableError, OPENAI_REASONING_EFFORTS, type OpenAIReasoningEffort, type OpenAITokenUsage, type OpenAiSttOptions, type OpenAiSttResult, type OpenAiTtsAudioOptions, type OpenAiTtsVoice, OpenAiVoiceAgent, type PeakPricing, type PricingUnit, type ProviderSchema, type TokenUsage as ProviderTokenUsage, type ProviderType, QwenAgent, REASONING_EFFORT_SCALE, type ReasoningEffort, SUPPORTED_VOICE_PROVIDERS, type SpeechRequest, type SpeechResult, type SpeechUsage, SupportedAiKeyNames, SupportedAiModels, type TokenPairUsage, type TokenUsage$1 as TokenUsage, type TranscriptionRequest, type TranscriptionResult, type TranscriptionUsage, VOICE_MODEL_CONSTANTS, VOICE_MODEL_PRICING, VOICE_PROVIDER_API_KEY, type VoiceAgent, VoiceAgentFactory, type VoiceModelPricing, type VoiceProvider, ZodSchemaConverter, buildGoogleTtsPrompt, calculateAnthropicCost, calculateCost, calculateDeepSeekCost, calculateGeminiSttCost, calculateGeminiTtsCost, calculateGoogleCost, calculateGrokCost, calculateKimiCost, calculateMistralCost, calculateModelCost, calculateOpenAICost, calculateOpenAiSttCost, calculateOpenAiTtsCost, clampReasoningEffort, cleanResponse, createCatalog, createVoiceAgent, extractAnthropicTokenUsage, extractTokenUsageFromResponse$2 as extractAnthropicTokenUsageFromResponse, extractDeepSeekTokenUsage, extractTokenUsageFromResponse$5 as extractDeepSeekTokenUsageFromResponse, extractFirstJsonObject, extractGoogleTokenUsage, extractTokenUsageFromResponse$1 as extractGoogleTokenUsageFromResponse, extractGrokTokenUsage, extractTokenUsageFromResponse$3 as extractGrokTokenUsageFromResponse, extractKimiTokenUsage, extractTokenUsageFromResponse$4 as extractKimiTokenUsageFromResponse, extractMistralTokenUsage, extractTokenUsageFromResponse as extractMistralTokenUsageFromResponse, extractOpenAITokenUsage, extractTokenUsageFromResponse$6 as extractOpenAITokenUsageFromResponse, extractTokenUsage, extractUsageAndCalculateCost, generateGoogleTtsAudio, generateOpenAiTtsAudio, generateSchemaInstructions, getModelConfigByApiName, getModelDisplayName, getModelProviderName, getModelTags, getProviderSignatureFields, isHybridThinkingModel, isInPeakWindow, isPeakBilling, isWeekendAt, logger, mergeThinking, modelHasTag, modelIsFast, needsPromptBasedSchema, parseAndValidateLlmJson, pcmToWav, safeValidateResponse, setLlmLogger, stableHashHex, stripInlineThinking, supportsNativeJsonSchema, toAnthropicEffort, toDeepSeekEffort, toFuguEffort, toGeminiEffort, toGlmEffort, toOpenAIEffort, transcribeWithGemini, transcribeWithOpenAi, validateResponse };
|
|
1576
|
+
export { type AIMessage, ANTHROPIC_REASONING_EFFORTS, API_KEY_CONSTANTS, AbstractAgent, type AgentActivityData, AgentFactory, type AgentLoggingConfig, type AnthropicReasoningEffort, type AnthropicTokenUsage, type ApiKeyMap, BotResponseError, BudgetController, type BudgetControllerOptions, BudgetExceededError, type BudgetLimit, type BudgetVerdict, CACHE_TIER_MARKER, ClaudeAgent, type CostCalculationOptions, DEEPSEEK_PEAK_SCHEDULE, DEEPSEEK_REASONING_EFFORTS, DEFAULT_LOGGING_CONFIG, DEFAULT_MAX_OUTPUT_TOKENS, type DeepSeekReasoningEffort, type DeepSeekTokenUsage, DeepSeekV2Agent, FUGU_REASONING_EFFORTS, FuguAgent, type FuguReasoningEffort, GEMINI_AUDIO_TOKENS_PER_SECOND, GEMINI_REASONING_EFFORTS, GLM_REASONING_EFFORTS, type GeminiReasoningEffort, GlmAgent, type GlmReasoningEffort, GoogleAgent, type GoogleSttOptions, type GoogleSttResult, type GoogleTokenUsage, type GoogleTtsAudioOptions, type GoogleTtsResult, GoogleVoiceAgent, Gpt5Agent, GrokAgent, type GrokTokenUsage, InMemorySpendStore, type JsonSchemaOptions, KimiAgent, type KimiTokenUsage, type LLMModel, LLM_CONSTANTS, type LlmLogger, type LoggingConfig, MESSAGE_ROLE, MODEL_PRICING, MiniMaxAgent, MistralAgent, type MistralTokenUsage, type Modality, ModelAuthenticationError, type ModelConfig, ModelError, ModelInvalidResponseError, ModelOverloadError, type ModelPricing, ModelQuotaExceededError, ModelRateLimitError, ModelRefusalError, type ModelTag, ModelUnavailableError, OPENAI_REASONING_EFFORTS, type OpenAIReasoningEffort, type OpenAITokenUsage, type OpenAiSttOptions, type OpenAiSttResult, type OpenAiTtsAudioOptions, type OpenAiTtsVoice, OpenAiVoiceAgent, type PeakPricing, type PricingUnit, type ProviderSchema, type TokenUsage as ProviderTokenUsage, type ProviderType, QwenAgent, REASONING_EFFORT_SCALE, type ReasoningEffort, SUPPORTED_VOICE_PROVIDERS, type SpeechRequest, type SpeechResult, type SpeechUsage, type SpendInput, type SpendKind, type SpendLedger, type SpendLedgers, type SpendStore, type SpendWindow, SupportedAiKeyNames, SupportedAiModels, type TokenPairUsage, type TokenUsage$1 as TokenUsage, type TranscriptionRequest, type TranscriptionResult, type TranscriptionUsage, VOICE_MODEL_CONSTANTS, VOICE_MODEL_PRICING, VOICE_PROVIDER_API_KEY, type VoiceAgent, VoiceAgentFactory, type VoiceModelPricing, type VoiceProvider, ZodSchemaConverter, applySpend, buildGoogleTtsPrompt, calculateAnthropicCost, calculateCost, calculateDeepSeekCost, calculateGeminiSttCost, calculateGeminiTtsCost, calculateGoogleCost, calculateGrokCost, calculateKimiCost, calculateMistralCost, calculateModelCost, calculateOpenAICost, calculateOpenAiSttCost, calculateOpenAiTtsCost, clampReasoningEffort, cleanResponse, createCatalog, createVoiceAgent, evaluateBudget, extractAnthropicTokenUsage, extractTokenUsageFromResponse$2 as extractAnthropicTokenUsageFromResponse, extractDeepSeekTokenUsage, extractTokenUsageFromResponse$5 as extractDeepSeekTokenUsageFromResponse, extractFirstJsonObject, extractGoogleTokenUsage, extractTokenUsageFromResponse$1 as extractGoogleTokenUsageFromResponse, extractGrokTokenUsage, extractTokenUsageFromResponse$3 as extractGrokTokenUsageFromResponse, extractKimiTokenUsage, extractTokenUsageFromResponse$4 as extractKimiTokenUsageFromResponse, extractMistralTokenUsage, extractTokenUsageFromResponse as extractMistralTokenUsageFromResponse, extractOpenAITokenUsage, extractTokenUsageFromResponse$6 as extractOpenAITokenUsageFromResponse, extractTokenUsage, extractUsageAndCalculateCost, firstRefusal, generateGoogleTtsAudio, generateOpenAiTtsAudio, generateSchemaInstructions, getModelConfigByApiName, getModelDisplayName, getModelProviderName, getModelTags, getProviderSignatureFields, isBudgetExceededError, isHybridThinkingModel, isInPeakWindow, isPeakBilling, isWeekendAt, ledgerSpend, logger, mergeThinking, modelHasTag, modelIsFast, needsPromptBasedSchema, parseAndValidateLlmJson, pcmToWav, periodEnd, periodKey, safeValidateResponse, setLlmLogger, stableHashHex, stripInlineThinking, supportsNativeJsonSchema, toAnthropicEffort, toDeepSeekEffort, toFuguEffort, toGeminiEffort, toGlmEffort, toOpenAIEffort, transcribeWithGemini, transcribeWithOpenAi, validateResponse };
|