caplyr 0.1.2 → 0.1.7

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 CHANGED
@@ -2,7 +2,7 @@
2
2
 
3
3
  **AI Cost Control Plane** — Stop runaway API bills automatically.
4
4
 
5
- Caplyr sits between your app and AI providers, controlling how requests execute based on cost constraints. Budget guardrails, auto-downgrade, and kill switch — in 2 lines of code.
5
+ Caplyr wraps your AI client and enforces cost controls based on your project settings in the Caplyr dashboard. Budget guardrails, auto-downgrade, and kill switch — in 2 lines of code.
6
6
 
7
7
  ## Install
8
8
 
@@ -18,9 +18,10 @@ import { protect } from "caplyr";
18
18
 
19
19
  // Wrap your client — everything else stays the same
20
20
  const client = protect(new Anthropic(), {
21
- apiKey: "caplyr_...", // Get yours at https://app.caplyr.com
22
- budget: 500, // Monthly cap in dollars
23
- fallback: "claude-haiku-3-5-20241022", // Auto-downgrade target
21
+ apiKey: "caplyr_...", // Get yours at https://app.caplyr.com
22
+ mode: "cost_protect", // Enforce budget limits
23
+ budget: { monthly: 500, daily: 50 }, // Budget caps in dollars
24
+ fallback: "claude-haiku-4-5-20251001", // Auto-downgrade target
24
25
  });
25
26
 
26
27
  // Use exactly as before — Caplyr is invisible
@@ -39,7 +40,8 @@ import { protect } from "caplyr";
39
40
 
40
41
  const client = protect(new OpenAI(), {
41
42
  apiKey: "caplyr_...",
42
- budget: 500,
43
+ mode: "cost_protect",
44
+ budget: { monthly: 500 },
43
45
  fallback: "gpt-4o-mini",
44
46
  });
45
47
 
@@ -53,42 +55,57 @@ const response = await client.chat.completions.create({
53
55
 
54
56
  | Feature | Description |
55
57
  |---------|-------------|
56
- | **Budget Guardrails** | Daily and monthly caps enforced at the SDK level. Hit the limit → requests are blocked or downgraded. |
58
+ | **Budget Guardrails** | Daily and monthly caps. Hit the limit → requests are blocked. Budget limits are configured in the Caplyr dashboard and enforced via the SDK. |
57
59
  | **Auto Downgrade** | When budget threshold is reached, automatically route to a cheaper model. Your app keeps working. |
58
- | **Kill Switch** | One-click emergency stop. Halts all AI API calls instantly. |
60
+ | **Kill Switch** | One-click emergency stop from the dashboard. Halts all AI API calls instantly. |
61
+
62
+ ## How Enforcement Works
63
+
64
+ Budget limits are managed **server-side** in your Caplyr project settings. The SDK sends your configured `budget` values to the server via heartbeats, and the server returns the current budget status (usage, limits, kill switch state). The SDK enforces limits locally based on that server response — the server is the source of truth, not the local config.
65
+
66
+ If you set `budget` in the SDK but have different limits in your dashboard, the dashboard settings take precedence.
59
67
 
60
68
  ## Modes
61
69
 
62
70
  ```typescript
63
- // Alert-only (default): observe and project, don't enforce
64
- protect(client, { apiKey: "...", mode: "alert_only" });
71
+ // Alert-only (default): observe and log, don't enforce
72
+ protect(client, { apiKey: "..." });
65
73
 
66
74
  // Cost protect: enforce budget caps and auto-downgrade
67
- protect(client, { apiKey: "...", mode: "cost_protect", budget: 500 });
75
+ protect(client, { apiKey: "...", mode: "cost_protect", budget: { monthly: 500 } });
68
76
  ```
69
77
 
78
+ > **Tip:** Setting `budget` without specifying `mode` will automatically enable `cost_protect`.
79
+
80
+ ## Currently Wrapped Endpoints
81
+
82
+ - **Anthropic:** `client.messages.create()`
83
+ - **OpenAI:** `client.chat.completions.create()`
84
+
85
+ Other endpoints (embeddings, images, Responses API) are not yet wrapped. Streaming requests (`stream: true`) are passed through but usage tracking may be incomplete.
86
+
70
87
  ## Configuration
71
88
 
72
89
  | Option | Type | Default | Description |
73
90
  |--------|------|---------|-------------|
74
91
  | `apiKey` | `string` | required | Your Caplyr project API key |
75
- | `budget` | `number` | — | Monthly budget cap in dollars |
76
- | `dailyBudget` | `number` | — | Daily budget cap in dollars |
92
+ | `budget` | `{ monthly?: number, daily?: number }` | — | Budget caps in dollars |
77
93
  | `fallback` | `string` | auto | Fallback model for auto-downgrade |
78
- | `mode` | `string` | `"alert_only"` | `"alert_only"` \| `"cost_protect"` \| `"kill_switch"` |
94
+ | `mode` | `"alert_only" \| "cost_protect"` | `"alert_only"` | Enforcement mode |
79
95
  | `downgradeThreshold` | `number` | `0.8` | Budget % at which downgrade activates |
80
96
  | `endpoint_tag` | `string` | — | Custom tag for cost attribution |
97
+ | `dashboardUrl` | `string` | `https://app.caplyr.com` | Dashboard URL for error messages |
98
+ | `endpoint` | `string` | `https://api.caplyr.com` | API endpoint for heartbeat/ingestion |
81
99
 
82
100
  ## Handling Blocked Requests
83
101
 
84
- When a request is blocked, Caplyr throws a structured error:
102
+ When a request is blocked in `cost_protect` mode, Caplyr throws a structured error:
85
103
 
86
104
  ```typescript
87
105
  try {
88
106
  const response = await client.messages.create({ ... });
89
107
  } catch (err) {
90
108
  if (err.caplyr) {
91
- // Caplyr enforcement event
92
109
  console.log(err.caplyr.code); // "BUDGET_EXCEEDED" | "KILL_SWITCH_ACTIVE"
93
110
  console.log(err.caplyr.retry_after); // ISO timestamp for next reset
94
111
  console.log(err.caplyr.budget_used); // Current spend
@@ -96,19 +113,28 @@ try {
96
113
  }
97
114
  ```
98
115
 
116
+ In `alert_only` mode, the request proceeds and the `onEnforcement` callback is fired instead.
117
+
99
118
  ## Shutdown
100
119
 
120
+ Caplyr does **not** register SIGINT/SIGTERM handlers — your app owns its process lifecycle. Call `shutdown()` in your own signal handler to flush pending logs before exit:
121
+
101
122
  ```typescript
102
123
  import { shutdown } from "caplyr";
103
124
 
104
- // Flush pending logs on app exit
105
- process.on("SIGTERM", () => shutdown());
125
+ async function gracefulShutdown() {
126
+ await shutdown();
127
+ process.exit(0);
128
+ }
129
+
130
+ process.on("SIGTERM", gracefulShutdown);
131
+ process.on("SIGINT", gracefulShutdown);
106
132
  ```
107
133
 
108
134
  ## Links
109
135
 
110
136
  - **Dashboard**: [app.caplyr.com](https://app.caplyr.com)
111
- - **Docs**: [docs.caplyr.com](https://docs.caplyr.com)
137
+ - **Docs**: [caplyr.com/docs](https://caplyr.com/docs)
112
138
  - **Website**: [caplyr.com](https://caplyr.com)
113
139
 
114
140
  ## License
package/dist/index.d.mts CHANGED
@@ -1,168 +1,53 @@
1
- /** Protection status of the SDK connection */
2
- type ProtectionStatus = "ACTIVE" | "DEGRADED" | "OFF";
3
- /** Supported AI providers */
4
- type Provider = "anthropic" | "openai";
5
- /** Operating mode for the SDK */
6
- type CaplyrMode = "cost_protect" | "alert_only" | "kill_switch";
7
- /** Configuration for the protect() function */
8
1
  interface CaplyrConfig {
9
- /** Your Caplyr project API key */
10
2
  apiKey: string;
11
- /** Caplyr backend URL (defaults to https://api.caplyr.com) */
12
- endpoint?: string;
13
- /** Operating mode (defaults to "alert_only" for trust-building) */
14
- mode?: CaplyrMode;
15
- /** Monthly budget cap in dollars */
16
- budget?: number;
17
- /** Daily budget cap in dollars */
18
- dailyBudget?: number;
19
- /** Fallback model when budget is approached/exceeded */
20
- fallback?: string;
21
- /** Budget threshold (0-1) at which auto-downgrade activates (default: 0.8) */
3
+ mode?: 'alert_only' | 'cost_protect';
4
+ /** Budget limits. Enforcement is server-side, based on project settings tied to your API key. */
5
+ budget?: {
6
+ daily?: number;
7
+ monthly?: number;
8
+ };
22
9
  downgradeThreshold?: number;
23
- /** Custom endpoint tag for attribution */
10
+ fallback?: string;
11
+ /** API endpoint for heartbeat and log ingestion (default: https://api.caplyr.com) */
12
+ endpoint?: string;
13
+ /** Dashboard URL for error messages (default: https://app.caplyr.com) */
14
+ dashboardUrl?: string;
24
15
  endpoint_tag?: string;
25
- /** Batch size before flushing logs (default: 10) */
26
16
  batchSize?: number;
27
- /** Max interval in ms between log flushes (default: 30000) */
28
17
  flushInterval?: number;
29
- /** Heartbeat interval in ms (default: 60000) */
30
18
  heartbeatInterval?: number;
31
- /** Called when protection status changes */
32
- onStatusChange?: (status: ProtectionStatus) => void;
33
- /** Called when an enforcement event occurs */
34
- onEnforcement?: (event: EnforcementEvent) => void;
35
- /** Called when an error occurs in the SDK (non-blocking) */
36
- onError?: (error: Error) => void;
37
- }
38
- /** Metadata captured for every AI API call */
39
- interface RequestLog {
40
- /** Unique request ID */
41
- id: string;
42
- /** Timestamp of the request */
43
- timestamp: number;
44
- /** Provider: "anthropic" | "openai" */
45
- provider: Provider;
46
- /** Model used (e.g., "claude-sonnet-4-20250514") */
47
- model: string;
48
- /** Input tokens consumed */
49
- input_tokens: number;
50
- /** Output tokens consumed */
51
- output_tokens: number;
52
- /** Calculated cost in dollars */
53
- cost: number;
54
- /** Request latency in milliseconds */
55
- latency_ms: number;
56
- /** Endpoint tag for attribution */
57
- endpoint_tag?: string;
58
- /** Whether this request was auto-downgraded */
59
- downgraded: boolean;
60
- /** Original model if downgraded */
61
- original_model?: string;
62
- /** Whether this request was blocked */
63
- blocked: boolean;
64
- /** Reason for block/downgrade if applicable */
65
- enforcement_reason?: string;
19
+ onError?: (err: Error) => void;
20
+ onEnforcement?: (event: any) => void;
66
21
  }
67
- /** Enforcement event emitted when guardrails activate */
68
- interface EnforcementEvent {
69
- type: "downgrade" | "block" | "kill_switch";
70
- timestamp: number;
71
- reason: string;
72
- original_model?: string;
73
- fallback_model?: string;
74
- budget_used: number;
75
- budget_limit: number;
76
- estimated_savings: number;
22
+ interface ResolvedConfig extends CaplyrConfig {
23
+ mode: 'alert_only' | 'cost_protect';
24
+ downgradeThreshold: number;
25
+ dashboardUrl: string;
26
+ /** @internal Called by interceptors to track request count */
27
+ _onRequest?: () => void;
77
28
  }
78
- /** Budget status returned from the backend */
79
- interface BudgetStatus {
80
- daily_used: number;
81
- daily_limit: number | null;
82
- monthly_used: number;
83
- monthly_limit: number | null;
84
- status: ProtectionStatus;
85
- kill_switch_active: boolean;
86
- }
87
- /** Structured error returned when a request is blocked */
88
- interface CaplyrBlockedError {
89
- code: "BUDGET_EXCEEDED" | "KILL_SWITCH_ACTIVE" | "RATE_LIMITED";
90
- message: string;
91
- budget_used: number;
92
- budget_limit: number;
93
- retry_after?: string;
94
- dashboard_url: string;
95
- }
96
- /** Internal state of the SDK */
97
- interface CaplyrState {
98
- status: ProtectionStatus;
99
- mode: CaplyrMode;
29
+ declare function protect(client: any, config: CaplyrConfig): any;
30
+ declare function getStatus(apiKey: string): string;
31
+ declare function getState(apiKey: string): {
32
+ status: string;
33
+ mode: "alert_only" | "cost_protect";
100
34
  budget_daily_used: number;
101
35
  budget_monthly_used: number;
102
36
  kill_switch_active: boolean;
103
- last_heartbeat: number;
37
+ last_heartbeat: number | null;
104
38
  request_count: number;
105
39
  total_cost: number;
106
- total_savings: number;
107
- }
108
-
109
- /**
110
- * Wrap an AI provider client with Caplyr cost control.
111
- *
112
- * Returns a proxy that is type-compatible with the original client.
113
- * All existing code works unchanged — Caplyr is invisible to callers.
114
- *
115
- * @param client - An Anthropic or OpenAI client instance
116
- * @param config - Caplyr configuration
117
- * @returns A proxied client with cost control applied
118
- *
119
- * @example
120
- * ```ts
121
- * import Anthropic from "@anthropic-ai/sdk"
122
- * import { protect } from "caplyr"
123
- *
124
- * const client = protect(new Anthropic(), {
125
- * apiKey: "caplyr_...",
126
- * budget: 500,
127
- * })
128
- * ```
129
- */
130
- declare function protect<T>(client: T, config: CaplyrConfig): T;
131
- /**
132
- * Get the current protection status for a given API key.
133
- */
134
- declare function getStatus(apiKey: string): ProtectionStatus;
135
- /**
136
- * Get the full SDK state for a given API key.
137
- */
138
- declare function getState(apiKey: string): CaplyrState | null;
139
- /**
140
- * Flush all pending logs and stop background tasks.
141
- * Call this during application shutdown.
142
- */
40
+ } | null;
143
41
  declare function shutdown(apiKey?: string): Promise<void>;
144
42
 
43
+ /** Per-million-token pricing: { input, output } */
145
44
  interface ModelPricing {
146
45
  input: number;
147
46
  output: number;
148
47
  }
149
- /**
150
- * Calculate the cost of a single API request.
151
- * Returns cost in dollars.
152
- */
153
48
  declare function calculateCost(model: string, inputTokens: number, outputTokens: number): number;
154
- /**
155
- * Register a custom model with pricing at runtime.
156
- * Useful for new models not yet in the built-in table.
157
- */
158
49
  declare function registerModel(model: string, pricing: ModelPricing, fallback?: string): void;
159
- /**
160
- * Check if a model is known to the pricing table.
161
- */
162
50
  declare function isKnownModel(model: string): boolean;
163
- /**
164
- * Get pricing for a model. Returns null if unknown.
165
- */
166
51
  declare function getModelPricing(model: string): ModelPricing | null;
167
52
 
168
- export { type BudgetStatus, type CaplyrBlockedError, type CaplyrConfig, type CaplyrMode, type CaplyrState, type EnforcementEvent, type ProtectionStatus, type Provider, type RequestLog, calculateCost, getModelPricing, getState, getStatus, isKnownModel, protect, registerModel, shutdown };
53
+ export { type CaplyrConfig, type ResolvedConfig, calculateCost, getModelPricing, getState, getStatus, isKnownModel, protect, registerModel, shutdown };
package/dist/index.d.ts CHANGED
@@ -1,168 +1,53 @@
1
- /** Protection status of the SDK connection */
2
- type ProtectionStatus = "ACTIVE" | "DEGRADED" | "OFF";
3
- /** Supported AI providers */
4
- type Provider = "anthropic" | "openai";
5
- /** Operating mode for the SDK */
6
- type CaplyrMode = "cost_protect" | "alert_only" | "kill_switch";
7
- /** Configuration for the protect() function */
8
1
  interface CaplyrConfig {
9
- /** Your Caplyr project API key */
10
2
  apiKey: string;
11
- /** Caplyr backend URL (defaults to https://api.caplyr.com) */
12
- endpoint?: string;
13
- /** Operating mode (defaults to "alert_only" for trust-building) */
14
- mode?: CaplyrMode;
15
- /** Monthly budget cap in dollars */
16
- budget?: number;
17
- /** Daily budget cap in dollars */
18
- dailyBudget?: number;
19
- /** Fallback model when budget is approached/exceeded */
20
- fallback?: string;
21
- /** Budget threshold (0-1) at which auto-downgrade activates (default: 0.8) */
3
+ mode?: 'alert_only' | 'cost_protect';
4
+ /** Budget limits. Enforcement is server-side, based on project settings tied to your API key. */
5
+ budget?: {
6
+ daily?: number;
7
+ monthly?: number;
8
+ };
22
9
  downgradeThreshold?: number;
23
- /** Custom endpoint tag for attribution */
10
+ fallback?: string;
11
+ /** API endpoint for heartbeat and log ingestion (default: https://api.caplyr.com) */
12
+ endpoint?: string;
13
+ /** Dashboard URL for error messages (default: https://app.caplyr.com) */
14
+ dashboardUrl?: string;
24
15
  endpoint_tag?: string;
25
- /** Batch size before flushing logs (default: 10) */
26
16
  batchSize?: number;
27
- /** Max interval in ms between log flushes (default: 30000) */
28
17
  flushInterval?: number;
29
- /** Heartbeat interval in ms (default: 60000) */
30
18
  heartbeatInterval?: number;
31
- /** Called when protection status changes */
32
- onStatusChange?: (status: ProtectionStatus) => void;
33
- /** Called when an enforcement event occurs */
34
- onEnforcement?: (event: EnforcementEvent) => void;
35
- /** Called when an error occurs in the SDK (non-blocking) */
36
- onError?: (error: Error) => void;
37
- }
38
- /** Metadata captured for every AI API call */
39
- interface RequestLog {
40
- /** Unique request ID */
41
- id: string;
42
- /** Timestamp of the request */
43
- timestamp: number;
44
- /** Provider: "anthropic" | "openai" */
45
- provider: Provider;
46
- /** Model used (e.g., "claude-sonnet-4-20250514") */
47
- model: string;
48
- /** Input tokens consumed */
49
- input_tokens: number;
50
- /** Output tokens consumed */
51
- output_tokens: number;
52
- /** Calculated cost in dollars */
53
- cost: number;
54
- /** Request latency in milliseconds */
55
- latency_ms: number;
56
- /** Endpoint tag for attribution */
57
- endpoint_tag?: string;
58
- /** Whether this request was auto-downgraded */
59
- downgraded: boolean;
60
- /** Original model if downgraded */
61
- original_model?: string;
62
- /** Whether this request was blocked */
63
- blocked: boolean;
64
- /** Reason for block/downgrade if applicable */
65
- enforcement_reason?: string;
19
+ onError?: (err: Error) => void;
20
+ onEnforcement?: (event: any) => void;
66
21
  }
67
- /** Enforcement event emitted when guardrails activate */
68
- interface EnforcementEvent {
69
- type: "downgrade" | "block" | "kill_switch";
70
- timestamp: number;
71
- reason: string;
72
- original_model?: string;
73
- fallback_model?: string;
74
- budget_used: number;
75
- budget_limit: number;
76
- estimated_savings: number;
22
+ interface ResolvedConfig extends CaplyrConfig {
23
+ mode: 'alert_only' | 'cost_protect';
24
+ downgradeThreshold: number;
25
+ dashboardUrl: string;
26
+ /** @internal Called by interceptors to track request count */
27
+ _onRequest?: () => void;
77
28
  }
78
- /** Budget status returned from the backend */
79
- interface BudgetStatus {
80
- daily_used: number;
81
- daily_limit: number | null;
82
- monthly_used: number;
83
- monthly_limit: number | null;
84
- status: ProtectionStatus;
85
- kill_switch_active: boolean;
86
- }
87
- /** Structured error returned when a request is blocked */
88
- interface CaplyrBlockedError {
89
- code: "BUDGET_EXCEEDED" | "KILL_SWITCH_ACTIVE" | "RATE_LIMITED";
90
- message: string;
91
- budget_used: number;
92
- budget_limit: number;
93
- retry_after?: string;
94
- dashboard_url: string;
95
- }
96
- /** Internal state of the SDK */
97
- interface CaplyrState {
98
- status: ProtectionStatus;
99
- mode: CaplyrMode;
29
+ declare function protect(client: any, config: CaplyrConfig): any;
30
+ declare function getStatus(apiKey: string): string;
31
+ declare function getState(apiKey: string): {
32
+ status: string;
33
+ mode: "alert_only" | "cost_protect";
100
34
  budget_daily_used: number;
101
35
  budget_monthly_used: number;
102
36
  kill_switch_active: boolean;
103
- last_heartbeat: number;
37
+ last_heartbeat: number | null;
104
38
  request_count: number;
105
39
  total_cost: number;
106
- total_savings: number;
107
- }
108
-
109
- /**
110
- * Wrap an AI provider client with Caplyr cost control.
111
- *
112
- * Returns a proxy that is type-compatible with the original client.
113
- * All existing code works unchanged — Caplyr is invisible to callers.
114
- *
115
- * @param client - An Anthropic or OpenAI client instance
116
- * @param config - Caplyr configuration
117
- * @returns A proxied client with cost control applied
118
- *
119
- * @example
120
- * ```ts
121
- * import Anthropic from "@anthropic-ai/sdk"
122
- * import { protect } from "caplyr"
123
- *
124
- * const client = protect(new Anthropic(), {
125
- * apiKey: "caplyr_...",
126
- * budget: 500,
127
- * })
128
- * ```
129
- */
130
- declare function protect<T>(client: T, config: CaplyrConfig): T;
131
- /**
132
- * Get the current protection status for a given API key.
133
- */
134
- declare function getStatus(apiKey: string): ProtectionStatus;
135
- /**
136
- * Get the full SDK state for a given API key.
137
- */
138
- declare function getState(apiKey: string): CaplyrState | null;
139
- /**
140
- * Flush all pending logs and stop background tasks.
141
- * Call this during application shutdown.
142
- */
40
+ } | null;
143
41
  declare function shutdown(apiKey?: string): Promise<void>;
144
42
 
43
+ /** Per-million-token pricing: { input, output } */
145
44
  interface ModelPricing {
146
45
  input: number;
147
46
  output: number;
148
47
  }
149
- /**
150
- * Calculate the cost of a single API request.
151
- * Returns cost in dollars.
152
- */
153
48
  declare function calculateCost(model: string, inputTokens: number, outputTokens: number): number;
154
- /**
155
- * Register a custom model with pricing at runtime.
156
- * Useful for new models not yet in the built-in table.
157
- */
158
49
  declare function registerModel(model: string, pricing: ModelPricing, fallback?: string): void;
159
- /**
160
- * Check if a model is known to the pricing table.
161
- */
162
50
  declare function isKnownModel(model: string): boolean;
163
- /**
164
- * Get pricing for a model. Returns null if unknown.
165
- */
166
51
  declare function getModelPricing(model: string): ModelPricing | null;
167
52
 
168
- export { type BudgetStatus, type CaplyrBlockedError, type CaplyrConfig, type CaplyrMode, type CaplyrState, type EnforcementEvent, type ProtectionStatus, type Provider, type RequestLog, calculateCost, getModelPricing, getState, getStatus, isKnownModel, protect, registerModel, shutdown };
53
+ export { type CaplyrConfig, type ResolvedConfig, calculateCost, getModelPricing, getState, getStatus, isKnownModel, protect, registerModel, shutdown };