@hiper2d/ai-agents 0.2.1 → 0.3.1

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/dist/index.js CHANGED
@@ -35,6 +35,8 @@ __export(index_exports, {
35
35
  AbstractAgent: () => AbstractAgent,
36
36
  AgentFactory: () => AgentFactory,
37
37
  BotResponseError: () => BotResponseError,
38
+ BudgetController: () => BudgetController,
39
+ BudgetExceededError: () => BudgetExceededError,
38
40
  CACHE_TIER_MARKER: () => CACHE_TIER_MARKER,
39
41
  ClaudeAgent: () => ClaudeAgent,
40
42
  DEEPSEEK_PEAK_SCHEDULE: () => DEEPSEEK_PEAK_SCHEDULE,
@@ -52,6 +54,7 @@ __export(index_exports, {
52
54
  GoogleVoiceAgent: () => GoogleVoiceAgent,
53
55
  Gpt5Agent: () => Gpt5Agent,
54
56
  GrokAgent: () => GrokAgent,
57
+ InMemorySpendStore: () => InMemorySpendStore,
55
58
  KimiAgent: () => KimiAgent,
56
59
  LLM_CONSTANTS: () => LLM_CONSTANTS,
57
60
  MESSAGE_ROLE: () => MESSAGE_ROLE,
@@ -78,6 +81,7 @@ __export(index_exports, {
78
81
  VOICE_PROVIDER_API_KEY: () => VOICE_PROVIDER_API_KEY,
79
82
  VoiceAgentFactory: () => VoiceAgentFactory,
80
83
  ZodSchemaConverter: () => ZodSchemaConverter,
84
+ applySpend: () => applySpend,
81
85
  buildGoogleTtsPrompt: () => buildGoogleTtsPrompt,
82
86
  calculateAnthropicCost: () => calculateAnthropicCost,
83
87
  calculateCost: () => calculateCost,
@@ -96,6 +100,7 @@ __export(index_exports, {
96
100
  cleanResponse: () => cleanResponse,
97
101
  createCatalog: () => createCatalog,
98
102
  createVoiceAgent: () => createVoiceAgent,
103
+ evaluateBudget: () => evaluateBudget,
99
104
  extractAnthropicTokenUsage: () => extractAnthropicTokenUsage,
100
105
  extractAnthropicTokenUsageFromResponse: () => extractTokenUsageFromResponse5,
101
106
  extractDeepSeekTokenUsage: () => extractDeepSeekTokenUsage,
@@ -113,6 +118,7 @@ __export(index_exports, {
113
118
  extractOpenAITokenUsageFromResponse: () => extractTokenUsageFromResponse,
114
119
  extractTokenUsage: () => extractTokenUsage,
115
120
  extractUsageAndCalculateCost: () => extractUsageAndCalculateCost,
121
+ firstRefusal: () => firstRefusal,
116
122
  generateGoogleTtsAudio: () => generateGoogleTtsAudio,
117
123
  generateOpenAiTtsAudio: () => generateOpenAiTtsAudio,
118
124
  generateSchemaInstructions: () => generateSchemaInstructions,
@@ -121,10 +127,12 @@ __export(index_exports, {
121
127
  getModelProviderName: () => getModelProviderName,
122
128
  getModelTags: () => getModelTags,
123
129
  getProviderSignatureFields: () => getProviderSignatureFields,
130
+ isBudgetExceededError: () => isBudgetExceededError,
124
131
  isHybridThinkingModel: () => isHybridThinkingModel,
125
132
  isInPeakWindow: () => isInPeakWindow,
126
133
  isPeakBilling: () => isPeakBilling,
127
134
  isWeekendAt: () => isWeekendAt,
135
+ ledgerSpend: () => ledgerSpend,
128
136
  logger: () => logger,
129
137
  mergeThinking: () => mergeThinking,
130
138
  modelHasTag: () => modelHasTag,
@@ -132,7 +140,10 @@ __export(index_exports, {
132
140
  needsPromptBasedSchema: () => needsPromptBasedSchema,
133
141
  parseAndValidateLlmJson: () => parseAndValidateLlmJson,
134
142
  pcmToWav: () => pcmToWav,
143
+ periodEnd: () => periodEnd,
144
+ periodKey: () => periodKey,
135
145
  safeValidateResponse: () => safeValidateResponse,
146
+ setBeforeAskHook: () => setBeforeAskHook,
136
147
  setLlmLogger: () => setLlmLogger,
137
148
  stableHashHex: () => stableHashHex,
138
149
  stripInlineThinking: () => stripInlineThinking,
@@ -2026,7 +2037,157 @@ function createVoiceAgent(provider, apiKey) {
2026
2037
  return VoiceAgentFactory.createAgent(provider, apiKey);
2027
2038
  }
2028
2039
 
2040
+ // src/budget/index.ts
2041
+ function round6(n) {
2042
+ return parseFloat((Number(n) || 0).toFixed(6));
2043
+ }
2044
+ function periodKey(timestamp, window) {
2045
+ const d = new Date(timestamp);
2046
+ const y = d.getUTCFullYear();
2047
+ const m = String(d.getUTCMonth() + 1).padStart(2, "0");
2048
+ if (window === "month") {
2049
+ return `${y}-${m}`;
2050
+ }
2051
+ const day = String(d.getUTCDate()).padStart(2, "0");
2052
+ return `${y}-${m}-${day}`;
2053
+ }
2054
+ function periodEnd(timestamp, window) {
2055
+ const d = new Date(timestamp);
2056
+ if (window === "month") {
2057
+ return Date.UTC(d.getUTCFullYear(), d.getUTCMonth() + 1, 1);
2058
+ }
2059
+ return Date.UTC(d.getUTCFullYear(), d.getUTCMonth(), d.getUTCDate() + 1);
2060
+ }
2061
+ function normalizeLedger(ledger, period) {
2062
+ if (!ledger || ledger.period !== period) {
2063
+ return { period, totalUSD: 0, buckets: {} };
2064
+ }
2065
+ const buckets = {};
2066
+ for (const [k, v] of Object.entries(ledger.buckets ?? {})) {
2067
+ buckets[k] = round6(v);
2068
+ }
2069
+ return { period, totalUSD: round6(ledger.totalUSD ?? 0), buckets };
2070
+ }
2071
+ function applySpend(ledger, input) {
2072
+ const timestamp = input.timestamp ?? Date.now();
2073
+ const period = periodKey(timestamp, input.window);
2074
+ const current2 = normalizeLedger(ledger, period);
2075
+ const amount = round6(input.amountUSD);
2076
+ if (!(amount > 0)) {
2077
+ return current2;
2078
+ }
2079
+ const buckets = { ...current2.buckets };
2080
+ if (input.bucket) {
2081
+ buckets[input.bucket] = round6((buckets[input.bucket] ?? 0) + amount);
2082
+ }
2083
+ return { period, totalUSD: round6(current2.totalUSD + amount), buckets };
2084
+ }
2085
+ function ledgerSpend(ledger, window, timestamp = Date.now(), bucket) {
2086
+ const current2 = normalizeLedger(ledger, periodKey(timestamp, window));
2087
+ return bucket ? current2.buckets[bucket] ?? 0 : current2.totalUSD;
2088
+ }
2089
+ function evaluateBudget(spentUSD, limit, timestamp = Date.now()) {
2090
+ const spent = round6(spentUSD);
2091
+ const unlimited = !Number.isFinite(limit.limitUSD);
2092
+ const remaining = unlimited ? Number.POSITIVE_INFINITY : Math.max(0, round6(limit.limitUSD - spent));
2093
+ return {
2094
+ allowed: unlimited || spent < limit.limitUSD,
2095
+ window: limit.window,
2096
+ ...limit.bucket ? { bucket: limit.bucket } : {},
2097
+ limitUSD: limit.limitUSD,
2098
+ spentUSD: spent,
2099
+ remainingUSD: remaining,
2100
+ resetsAt: periodEnd(timestamp, limit.window)
2101
+ };
2102
+ }
2103
+ function firstRefusal(verdicts) {
2104
+ return verdicts.find((v) => !v.allowed);
2105
+ }
2106
+ var BudgetExceededError = class _BudgetExceededError extends Error {
2107
+ verdict;
2108
+ subject;
2109
+ constructor(verdict, subject, message) {
2110
+ super(message ?? _BudgetExceededError.describe(verdict));
2111
+ this.name = "BudgetExceededError";
2112
+ this.verdict = verdict;
2113
+ this.subject = subject;
2114
+ }
2115
+ static describe(v) {
2116
+ const when = v.window === "day" ? "daily" : "monthly";
2117
+ return `${when} budget of $${v.limitUSD} exhausted ($${v.spentUSD} spent); resets at ${new Date(v.resetsAt).toISOString()}`;
2118
+ }
2119
+ };
2120
+ function isBudgetExceededError(err) {
2121
+ return err instanceof BudgetExceededError || typeof err === "object" && err !== null && err.name === "BudgetExceededError" && !!err.verdict;
2122
+ }
2123
+ var InMemorySpendStore = class {
2124
+ ledgers = /* @__PURE__ */ new Map();
2125
+ async read(subject) {
2126
+ return { ...this.ledgers.get(subject) ?? {} };
2127
+ }
2128
+ async update(subject, fn) {
2129
+ const next = fn({ ...this.ledgers.get(subject) ?? {} });
2130
+ this.ledgers.set(subject, next);
2131
+ return { ...next };
2132
+ }
2133
+ };
2134
+ var BudgetController = class {
2135
+ constructor(store, options) {
2136
+ this.store = store;
2137
+ this.limits = options.limits;
2138
+ this.bucket = options.bucket;
2139
+ this.clock = options.clock ?? (() => Date.now());
2140
+ }
2141
+ store;
2142
+ limits;
2143
+ bucket;
2144
+ clock;
2145
+ verdicts(ledgers, now) {
2146
+ return this.limits.map((limit) => {
2147
+ const spent = ledgerSpend(ledgers[limit.window], limit.window, now, limit.bucket ?? this.bucket);
2148
+ return evaluateBudget(spent, limit, now);
2149
+ });
2150
+ }
2151
+ async check(subject) {
2152
+ return this.verdicts(await this.store.read(subject), this.clock());
2153
+ }
2154
+ async assertWithinBudget(subject) {
2155
+ const refused = firstRefusal(await this.check(subject));
2156
+ if (refused) {
2157
+ throw new BudgetExceededError(refused, subject);
2158
+ }
2159
+ }
2160
+ /**
2161
+ * Record `amountUSD` against every limited window. Re-checks the limits on the
2162
+ * ledgers as they are at write time and throws `BudgetExceededError` (writing
2163
+ * nothing) if any window is already exhausted.
2164
+ */
2165
+ async record(subject, amountUSD) {
2166
+ const now = this.clock();
2167
+ return this.store.update(subject, (current2) => {
2168
+ const refused = firstRefusal(this.verdicts(current2, now));
2169
+ if (refused) {
2170
+ throw new BudgetExceededError(refused, subject);
2171
+ }
2172
+ const next = { ...current2 };
2173
+ for (const limit of this.limits) {
2174
+ next[limit.window] = applySpend(current2[limit.window], {
2175
+ window: limit.window,
2176
+ amountUSD,
2177
+ bucket: limit.bucket ?? this.bucket,
2178
+ timestamp: now
2179
+ });
2180
+ }
2181
+ return next;
2182
+ });
2183
+ }
2184
+ };
2185
+
2029
2186
  // src/agents/abstract-agent.ts
2187
+ var beforeAskHook;
2188
+ function setBeforeAskHook(hook) {
2189
+ beforeAskHook = hook;
2190
+ }
2030
2191
  var AbstractAgent = class {
2031
2192
  name;
2032
2193
  gameId;
@@ -2078,6 +2239,7 @@ var AbstractAgent = class {
2078
2239
  * must NOT override these.
2079
2240
  */
2080
2241
  async askWithZodSchema(zodSchema, messages) {
2242
+ if (beforeAskHook) await beforeAskHook(this);
2081
2243
  const startedAt = Date.now();
2082
2244
  try {
2083
2245
  const [result, thinking, usage, signature] = await this.doAskWithZodSchema(zodSchema, messages);
@@ -2088,6 +2250,7 @@ var AbstractAgent = class {
2088
2250
  }
2089
2251
  }
2090
2252
  async askText(messages) {
2253
+ if (beforeAskHook) await beforeAskHook(this);
2091
2254
  const startedAt = Date.now();
2092
2255
  try {
2093
2256
  const [content, thinking, usage, signature] = await this.doAskText(messages);
@@ -4755,6 +4918,8 @@ var AgentFactory = class {
4755
4918
  AbstractAgent,
4756
4919
  AgentFactory,
4757
4920
  BotResponseError,
4921
+ BudgetController,
4922
+ BudgetExceededError,
4758
4923
  CACHE_TIER_MARKER,
4759
4924
  ClaudeAgent,
4760
4925
  DEEPSEEK_PEAK_SCHEDULE,
@@ -4772,6 +4937,7 @@ var AgentFactory = class {
4772
4937
  GoogleVoiceAgent,
4773
4938
  Gpt5Agent,
4774
4939
  GrokAgent,
4940
+ InMemorySpendStore,
4775
4941
  KimiAgent,
4776
4942
  LLM_CONSTANTS,
4777
4943
  MESSAGE_ROLE,
@@ -4798,6 +4964,7 @@ var AgentFactory = class {
4798
4964
  VOICE_PROVIDER_API_KEY,
4799
4965
  VoiceAgentFactory,
4800
4966
  ZodSchemaConverter,
4967
+ applySpend,
4801
4968
  buildGoogleTtsPrompt,
4802
4969
  calculateAnthropicCost,
4803
4970
  calculateCost,
@@ -4816,6 +4983,7 @@ var AgentFactory = class {
4816
4983
  cleanResponse,
4817
4984
  createCatalog,
4818
4985
  createVoiceAgent,
4986
+ evaluateBudget,
4819
4987
  extractAnthropicTokenUsage,
4820
4988
  extractAnthropicTokenUsageFromResponse,
4821
4989
  extractDeepSeekTokenUsage,
@@ -4833,6 +5001,7 @@ var AgentFactory = class {
4833
5001
  extractOpenAITokenUsageFromResponse,
4834
5002
  extractTokenUsage,
4835
5003
  extractUsageAndCalculateCost,
5004
+ firstRefusal,
4836
5005
  generateGoogleTtsAudio,
4837
5006
  generateOpenAiTtsAudio,
4838
5007
  generateSchemaInstructions,
@@ -4841,10 +5010,12 @@ var AgentFactory = class {
4841
5010
  getModelProviderName,
4842
5011
  getModelTags,
4843
5012
  getProviderSignatureFields,
5013
+ isBudgetExceededError,
4844
5014
  isHybridThinkingModel,
4845
5015
  isInPeakWindow,
4846
5016
  isPeakBilling,
4847
5017
  isWeekendAt,
5018
+ ledgerSpend,
4848
5019
  logger,
4849
5020
  mergeThinking,
4850
5021
  modelHasTag,
@@ -4852,7 +5023,10 @@ var AgentFactory = class {
4852
5023
  needsPromptBasedSchema,
4853
5024
  parseAndValidateLlmJson,
4854
5025
  pcmToWav,
5026
+ periodEnd,
5027
+ periodKey,
4855
5028
  safeValidateResponse,
5029
+ setBeforeAskHook,
4856
5030
  setLlmLogger,
4857
5031
  stableHashHex,
4858
5032
  stripInlineThinking,