caplyr 0.1.9 → 0.2.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 CHANGED
@@ -2,7 +2,7 @@
2
2
 
3
3
  **AI Cost Control Plane** — Stop runaway API bills automatically.
4
4
 
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.
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.
6
6
 
7
7
  ## Install
8
8
 
@@ -18,10 +18,9 @@ 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
- 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
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
25
24
  });
26
25
 
27
26
  // Use exactly as before — Caplyr is invisible
@@ -40,8 +39,7 @@ import { protect } from "caplyr";
40
39
 
41
40
  const client = protect(new OpenAI(), {
42
41
  apiKey: "caplyr_...",
43
- mode: "cost_protect",
44
- budget: { monthly: 500 },
42
+ budget: 500,
45
43
  fallback: "gpt-4o-mini",
46
44
  });
47
45
 
@@ -55,57 +53,42 @@ const response = await client.chat.completions.create({
55
53
 
56
54
  | Feature | Description |
57
55
  |---------|-------------|
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. |
56
+ | **Budget Guardrails** | Daily and monthly caps enforced at the SDK level. Hit the limit → requests are blocked or downgraded. |
59
57
  | **Auto Downgrade** | When budget threshold is reached, automatically route to a cheaper model. Your app keeps working. |
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.
58
+ | **Kill Switch** | One-click emergency stop. Halts all AI API calls instantly. |
67
59
 
68
60
  ## Modes
69
61
 
70
62
  ```typescript
71
- // Alert-only (default): observe and log, don't enforce
72
- protect(client, { apiKey: "..." });
63
+ // Alert-only (default): observe and project, don't enforce
64
+ protect(client, { apiKey: "...", mode: "alert_only" });
73
65
 
74
66
  // Cost protect: enforce budget caps and auto-downgrade
75
- protect(client, { apiKey: "...", mode: "cost_protect", budget: { monthly: 500 } });
67
+ protect(client, { apiKey: "...", mode: "cost_protect", budget: 500 });
76
68
  ```
77
69
 
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
-
87
70
  ## Configuration
88
71
 
89
72
  | Option | Type | Default | Description |
90
73
  |--------|------|---------|-------------|
91
74
  | `apiKey` | `string` | required | Your Caplyr project API key |
92
- | `budget` | `{ monthly?: number, daily?: number }` | — | Budget caps in dollars |
75
+ | `budget` | `number` | — | Monthly budget cap in dollars |
76
+ | `dailyBudget` | `number` | — | Daily budget cap in dollars |
93
77
  | `fallback` | `string` | auto | Fallback model for auto-downgrade |
94
- | `mode` | `"alert_only" \| "cost_protect"` | `"alert_only"` | Enforcement mode |
78
+ | `mode` | `string` | `"alert_only"` | `"alert_only"` \| `"cost_protect"` \| `"kill_switch"` |
95
79
  | `downgradeThreshold` | `number` | `0.8` | Budget % at which downgrade activates |
96
80
  | `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 |
99
81
 
100
82
  ## Handling Blocked Requests
101
83
 
102
- When a request is blocked in `cost_protect` mode, Caplyr throws a structured error:
84
+ When a request is blocked, Caplyr throws a structured error:
103
85
 
104
86
  ```typescript
105
87
  try {
106
88
  const response = await client.messages.create({ ... });
107
89
  } catch (err) {
108
90
  if (err.caplyr) {
91
+ // Caplyr enforcement event
109
92
  console.log(err.caplyr.code); // "BUDGET_EXCEEDED" | "KILL_SWITCH_ACTIVE"
110
93
  console.log(err.caplyr.retry_after); // ISO timestamp for next reset
111
94
  console.log(err.caplyr.budget_used); // Current spend
@@ -113,28 +96,19 @@ try {
113
96
  }
114
97
  ```
115
98
 
116
- In `alert_only` mode, the request proceeds and the `onEnforcement` callback is fired instead.
117
-
118
99
  ## Shutdown
119
100
 
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
-
122
101
  ```typescript
123
102
  import { shutdown } from "caplyr";
124
103
 
125
- async function gracefulShutdown() {
126
- await shutdown();
127
- process.exit(0);
128
- }
129
-
130
- process.on("SIGTERM", gracefulShutdown);
131
- process.on("SIGINT", gracefulShutdown);
104
+ // Flush pending logs on app exit
105
+ process.on("SIGTERM", () => shutdown());
132
106
  ```
133
107
 
134
108
  ## Links
135
109
 
136
110
  - **Dashboard**: [app.caplyr.com](https://app.caplyr.com)
137
- - **Docs**: [caplyr.com/docs](https://caplyr.com/docs)
111
+ - **Docs**: [docs.caplyr.com](https://docs.caplyr.com)
138
112
  - **Website**: [caplyr.com](https://caplyr.com)
139
113
 
140
114
  ## License
package/dist/index.d.mts CHANGED
@@ -1,53 +1,168 @@
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 */
1
8
  interface CaplyrConfig {
9
+ /** Your Caplyr project API key */
2
10
  apiKey: string;
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
- };
9
- downgradeThreshold?: number;
10
- fallback?: string;
11
- /** API endpoint for heartbeat and log ingestion (default: https://caplyr.com) */
11
+ /** Caplyr backend URL (defaults to https://api.caplyr.com) */
12
12
  endpoint?: string;
13
- /** Dashboard URL for error messages (default: https://app.caplyr.com) */
14
- dashboardUrl?: 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) */
22
+ downgradeThreshold?: number;
23
+ /** Custom endpoint tag for attribution */
15
24
  endpoint_tag?: string;
25
+ /** Batch size before flushing logs (default: 10) */
16
26
  batchSize?: number;
27
+ /** Max interval in ms between log flushes (default: 30000) */
17
28
  flushInterval?: number;
29
+ /** Heartbeat interval in ms (default: 60000) */
18
30
  heartbeatInterval?: number;
19
- onError?: (err: Error) => void;
20
- onEnforcement?: (event: any) => void;
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;
21
66
  }
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;
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;
28
77
  }
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";
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;
34
100
  budget_daily_used: number;
35
101
  budget_monthly_used: number;
36
102
  kill_switch_active: boolean;
37
- last_heartbeat: number | null;
103
+ last_heartbeat: number;
38
104
  request_count: number;
39
105
  total_cost: number;
40
- } | null;
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
+ */
41
143
  declare function shutdown(apiKey?: string): Promise<void>;
42
144
 
43
- /** Per-million-token pricing: { input, output } */
44
145
  interface ModelPricing {
45
146
  input: number;
46
147
  output: number;
47
148
  }
149
+ /**
150
+ * Calculate the cost of a single API request.
151
+ * Returns cost in dollars.
152
+ */
48
153
  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
+ */
49
158
  declare function registerModel(model: string, pricing: ModelPricing, fallback?: string): void;
159
+ /**
160
+ * Check if a model is known to the pricing table.
161
+ */
50
162
  declare function isKnownModel(model: string): boolean;
163
+ /**
164
+ * Get pricing for a model. Returns null if unknown.
165
+ */
51
166
  declare function getModelPricing(model: string): ModelPricing | null;
52
167
 
53
- export { type CaplyrConfig, type ResolvedConfig, calculateCost, getModelPricing, getState, getStatus, isKnownModel, protect, registerModel, shutdown };
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 };
package/dist/index.d.ts CHANGED
@@ -1,53 +1,168 @@
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 */
1
8
  interface CaplyrConfig {
9
+ /** Your Caplyr project API key */
2
10
  apiKey: string;
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
- };
9
- downgradeThreshold?: number;
10
- fallback?: string;
11
- /** API endpoint for heartbeat and log ingestion (default: https://caplyr.com) */
11
+ /** Caplyr backend URL (defaults to https://api.caplyr.com) */
12
12
  endpoint?: string;
13
- /** Dashboard URL for error messages (default: https://app.caplyr.com) */
14
- dashboardUrl?: 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) */
22
+ downgradeThreshold?: number;
23
+ /** Custom endpoint tag for attribution */
15
24
  endpoint_tag?: string;
25
+ /** Batch size before flushing logs (default: 10) */
16
26
  batchSize?: number;
27
+ /** Max interval in ms between log flushes (default: 30000) */
17
28
  flushInterval?: number;
29
+ /** Heartbeat interval in ms (default: 60000) */
18
30
  heartbeatInterval?: number;
19
- onError?: (err: Error) => void;
20
- onEnforcement?: (event: any) => void;
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;
21
66
  }
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;
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;
28
77
  }
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";
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;
34
100
  budget_daily_used: number;
35
101
  budget_monthly_used: number;
36
102
  kill_switch_active: boolean;
37
- last_heartbeat: number | null;
103
+ last_heartbeat: number;
38
104
  request_count: number;
39
105
  total_cost: number;
40
- } | null;
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
+ */
41
143
  declare function shutdown(apiKey?: string): Promise<void>;
42
144
 
43
- /** Per-million-token pricing: { input, output } */
44
145
  interface ModelPricing {
45
146
  input: number;
46
147
  output: number;
47
148
  }
149
+ /**
150
+ * Calculate the cost of a single API request.
151
+ * Returns cost in dollars.
152
+ */
48
153
  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
+ */
49
158
  declare function registerModel(model: string, pricing: ModelPricing, fallback?: string): void;
159
+ /**
160
+ * Check if a model is known to the pricing table.
161
+ */
50
162
  declare function isKnownModel(model: string): boolean;
163
+ /**
164
+ * Get pricing for a model. Returns null if unknown.
165
+ */
51
166
  declare function getModelPricing(model: string): ModelPricing | null;
52
167
 
53
- export { type CaplyrConfig, type ResolvedConfig, calculateCost, getModelPricing, getState, getStatus, isKnownModel, protect, registerModel, shutdown };
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 };