caplyr 0.2.0 → 0.2.2

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
@@ -124,12 +124,30 @@ var Heartbeat = class {
124
124
  };
125
125
  /** Current protection status */
126
126
  this.status = "ACTIVE";
127
- this.endpoint = config.endpoint ?? "https://api.caplyr.com";
127
+ /** Local budget limits set via config (not from server) */
128
+ this.localDailyLimit = null;
129
+ this.localMonthlyLimit = null;
130
+ this.endpoint = config.endpoint ?? "https://caplyr.com";
128
131
  this.apiKey = config.apiKey;
129
132
  this.interval = config.heartbeatInterval ?? 6e4;
130
133
  this.onStatusChange = config.onStatusChange;
131
134
  this.onError = config.onError;
132
135
  }
136
+ /**
137
+ * Apply local budget limits from config.
138
+ * These act as client-side enforcement even if the server
139
+ * doesn't return limits (e.g. during heartbeat failures).
140
+ */
141
+ applyLocalLimits(budget) {
142
+ if (budget.daily !== void 0) {
143
+ this.localDailyLimit = budget.daily;
144
+ this.budgetStatus.daily_limit = budget.daily;
145
+ }
146
+ if (budget.monthly !== void 0) {
147
+ this.localMonthlyLimit = budget.monthly;
148
+ this.budgetStatus.monthly_limit = budget.monthly;
149
+ }
150
+ }
133
151
  /**
134
152
  * Start the heartbeat loop.
135
153
  * Immediately sends first heartbeat, then repeats on interval.
@@ -156,7 +174,17 @@ var Heartbeat = class {
156
174
  throw new Error(`Heartbeat failed: ${res.status}`);
157
175
  }
158
176
  const data = await res.json();
159
- this.budgetStatus = data;
177
+ const localDailyUsed = this.budgetStatus.daily_used;
178
+ const localMonthlyUsed = this.budgetStatus.monthly_used;
179
+ this.budgetStatus = {
180
+ ...data,
181
+ // Use whichever spend is higher — server or local tracking
182
+ daily_used: Math.max(data.daily_used ?? 0, localDailyUsed),
183
+ monthly_used: Math.max(data.monthly_used ?? 0, localMonthlyUsed),
184
+ // Preserve local limits if server doesn't provide them
185
+ daily_limit: data.daily_limit ?? this.localDailyLimit,
186
+ monthly_limit: data.monthly_limit ?? this.localMonthlyLimit
187
+ };
160
188
  this.consecutiveFailures = 0;
161
189
  const newStatus = data.kill_switch_active ? "OFF" : data.status;
162
190
  if (newStatus !== this.status) {
@@ -665,8 +693,9 @@ function protect(client, config) {
665
693
  "Caplyr: apiKey is required. Get yours at https://app.caplyr.com"
666
694
  );
667
695
  }
696
+ const userWantsProtect = config.budget && !config.mode;
668
697
  const resolvedConfig = {
669
- mode: config.budget && !config.mode ? "cost_protect" : "alert_only",
698
+ mode: userWantsProtect ? "cost_protect" : "alert_only",
670
699
  downgradeThreshold: 0.8,
671
700
  batchSize: 10,
672
701
  flushInterval: 3e4,
@@ -682,6 +711,10 @@ function protect(client, config) {
682
711
  instances.set(resolvedConfig.apiKey, shared);
683
712
  }
684
713
  const { shipper, heartbeat } = shared;
714
+ if (resolvedConfig.budget) {
715
+ const budgetConfig = typeof resolvedConfig.budget === "number" ? { monthly: resolvedConfig.budget } : resolvedConfig.budget;
716
+ heartbeat.applyLocalLimits(budgetConfig);
717
+ }
685
718
  const provider = detectProvider(client);
686
719
  switch (provider) {
687
720
  case "anthropic":
package/dist/index.mjs CHANGED
@@ -91,12 +91,30 @@ var Heartbeat = class {
91
91
  };
92
92
  /** Current protection status */
93
93
  this.status = "ACTIVE";
94
- this.endpoint = config.endpoint ?? "https://api.caplyr.com";
94
+ /** Local budget limits set via config (not from server) */
95
+ this.localDailyLimit = null;
96
+ this.localMonthlyLimit = null;
97
+ this.endpoint = config.endpoint ?? "https://caplyr.com";
95
98
  this.apiKey = config.apiKey;
96
99
  this.interval = config.heartbeatInterval ?? 6e4;
97
100
  this.onStatusChange = config.onStatusChange;
98
101
  this.onError = config.onError;
99
102
  }
103
+ /**
104
+ * Apply local budget limits from config.
105
+ * These act as client-side enforcement even if the server
106
+ * doesn't return limits (e.g. during heartbeat failures).
107
+ */
108
+ applyLocalLimits(budget) {
109
+ if (budget.daily !== void 0) {
110
+ this.localDailyLimit = budget.daily;
111
+ this.budgetStatus.daily_limit = budget.daily;
112
+ }
113
+ if (budget.monthly !== void 0) {
114
+ this.localMonthlyLimit = budget.monthly;
115
+ this.budgetStatus.monthly_limit = budget.monthly;
116
+ }
117
+ }
100
118
  /**
101
119
  * Start the heartbeat loop.
102
120
  * Immediately sends first heartbeat, then repeats on interval.
@@ -123,7 +141,17 @@ var Heartbeat = class {
123
141
  throw new Error(`Heartbeat failed: ${res.status}`);
124
142
  }
125
143
  const data = await res.json();
126
- this.budgetStatus = data;
144
+ const localDailyUsed = this.budgetStatus.daily_used;
145
+ const localMonthlyUsed = this.budgetStatus.monthly_used;
146
+ this.budgetStatus = {
147
+ ...data,
148
+ // Use whichever spend is higher — server or local tracking
149
+ daily_used: Math.max(data.daily_used ?? 0, localDailyUsed),
150
+ monthly_used: Math.max(data.monthly_used ?? 0, localMonthlyUsed),
151
+ // Preserve local limits if server doesn't provide them
152
+ daily_limit: data.daily_limit ?? this.localDailyLimit,
153
+ monthly_limit: data.monthly_limit ?? this.localMonthlyLimit
154
+ };
127
155
  this.consecutiveFailures = 0;
128
156
  const newStatus = data.kill_switch_active ? "OFF" : data.status;
129
157
  if (newStatus !== this.status) {
@@ -632,8 +660,9 @@ function protect(client, config) {
632
660
  "Caplyr: apiKey is required. Get yours at https://app.caplyr.com"
633
661
  );
634
662
  }
663
+ const userWantsProtect = config.budget && !config.mode;
635
664
  const resolvedConfig = {
636
- mode: config.budget && !config.mode ? "cost_protect" : "alert_only",
665
+ mode: userWantsProtect ? "cost_protect" : "alert_only",
637
666
  downgradeThreshold: 0.8,
638
667
  batchSize: 10,
639
668
  flushInterval: 3e4,
@@ -649,6 +678,10 @@ function protect(client, config) {
649
678
  instances.set(resolvedConfig.apiKey, shared);
650
679
  }
651
680
  const { shipper, heartbeat } = shared;
681
+ if (resolvedConfig.budget) {
682
+ const budgetConfig = typeof resolvedConfig.budget === "number" ? { monthly: resolvedConfig.budget } : resolvedConfig.budget;
683
+ heartbeat.applyLocalLimits(budgetConfig);
684
+ }
652
685
  const provider = detectProvider(client);
653
686
  switch (provider) {
654
687
  case "anthropic":
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "caplyr",
3
- "version": "0.2.0",
3
+ "version": "0.2.2",
4
4
  "description": "AI Cost Control Plane — budget guardrails, auto-downgrade, and kill switch for AI API calls",
5
5
  "main": "dist/index.js",
6
6
  "module": "dist/index.mjs",