@weckr/sdk 0.1.2 → 0.1.3
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 +35 -3
- package/dist/index.d.mts +60 -5
- package/dist/index.d.ts +60 -5
- package/dist/index.js +235 -85
- package/dist/index.mjs +233 -85
- package/package.json +12 -13
package/README.md
CHANGED
|
@@ -45,9 +45,41 @@ Sign up at [https://useweckr.com](https://useweckr.com).
|
|
|
45
45
|
|
|
46
46
|
## Supported providers
|
|
47
47
|
|
|
48
|
-
- **OpenAI** — `gpt-4o`, `gpt-4o-mini`, `gpt-3.5-turbo`
|
|
49
|
-
- **Anthropic** — `claude-opus-4`, `claude-sonnet-4`, `claude-haiku-4`
|
|
50
|
-
- **Gemini** — `gemini-2.5-
|
|
48
|
+
- **OpenAI** — `gpt-4o`, `gpt-4o-mini`, `gpt-4-turbo`, `gpt-4`, `gpt-3.5-turbo`, `o1-preview`, `o1-mini`
|
|
49
|
+
- **Anthropic** — `claude-opus-4`, `claude-sonnet-4`, `claude-haiku-4-5`, `claude-3-5-sonnet`, `claude-3-5-haiku`, `claude-3-opus`
|
|
50
|
+
- **Gemini** — `gemini-2.5-pro`, `gemini-2.5-flash`, `gemini-1.5-pro`, `gemini-1.5-flash`
|
|
51
|
+
|
|
52
|
+
Dated variants (`gpt-4o-2024-08-06`, `claude-3-5-sonnet-latest`, …) resolve to the matching family by longest-prefix lookup. Unknown models log `costUsd = 0` and don't trigger caps.
|
|
53
|
+
|
|
54
|
+
## Caps + downgrades
|
|
55
|
+
|
|
56
|
+
Set per-plan spending caps in the dashboard. When a user crosses their cap:
|
|
57
|
+
|
|
58
|
+
- `action: 'block'` → `wk.chat()` throws `WeckrCapError` (LLM call never made)
|
|
59
|
+
- `action: 'downgrade'` → the SDK silently swaps the model for a cheaper one in the same provider (`gpt-4o` → `gpt-4o-mini`, `claude-opus-4` → `claude-sonnet-4`, etc.)
|
|
60
|
+
|
|
61
|
+
Errors come in two flavors:
|
|
62
|
+
|
|
63
|
+
```ts
|
|
64
|
+
import { isWeckrCapError, isWeckrConfigError } from '@weckr/sdk';
|
|
65
|
+
|
|
66
|
+
try {
|
|
67
|
+
await wk.chat(openai, opts);
|
|
68
|
+
} catch (err) {
|
|
69
|
+
if (isWeckrCapError(err)) return showUpgradePrompt(err);
|
|
70
|
+
if (isWeckrConfigError(err)) return logBackendAlert(err); // typo'd api key, unknown plan
|
|
71
|
+
throw err; // real LLM error
|
|
72
|
+
}
|
|
73
|
+
```
|
|
74
|
+
|
|
75
|
+
## Short-lived processes (Lambda, cron, CLI)
|
|
76
|
+
|
|
77
|
+
`wk.chat()` returns as soon as the LLM call resolves; the log POST is fire-and-forget. In short-lived processes call `await wk.flush()` before exit to give the POSTs time to land:
|
|
78
|
+
|
|
79
|
+
```ts
|
|
80
|
+
await wk.chat(openai, opts);
|
|
81
|
+
await wk.flush(); // default 5s timeout
|
|
82
|
+
```
|
|
51
83
|
|
|
52
84
|
## What gets logged
|
|
53
85
|
|
package/dist/index.d.mts
CHANGED
|
@@ -1,6 +1,12 @@
|
|
|
1
|
-
|
|
1
|
+
/**
|
|
2
|
+
* 'unknown' is used when the SDK can't detect the provider via shape. We still
|
|
3
|
+
* log the row (so the dashboard sees something) but cost+downgrade lookup fall
|
|
4
|
+
* back to no-op behavior.
|
|
5
|
+
*/
|
|
6
|
+
type Provider = 'openai' | 'anthropic' | 'gemini' | 'unknown';
|
|
2
7
|
interface WeckrConfig {
|
|
3
8
|
apiKey: string;
|
|
9
|
+
/** Map of plan name -> monthly revenue per user. Required if you pass `plan` to chat(). */
|
|
4
10
|
plans?: Record<string, number>;
|
|
5
11
|
endpoint?: string;
|
|
6
12
|
/** Optional override for the cap-check endpoint. Derived from `endpoint` by default. */
|
|
@@ -11,17 +17,33 @@ interface WeckrConfig {
|
|
|
11
17
|
*/
|
|
12
18
|
disableCapCheck?: boolean;
|
|
13
19
|
fetch?: typeof fetch;
|
|
20
|
+
/**
|
|
21
|
+
* Async errors (cap-check network failure, log POST failure) are reported here.
|
|
22
|
+
* If absent, errors are silently swallowed. CRITICAL ones (401/403 on a
|
|
23
|
+
* misconfigured api key) still throw WeckrConfigError synchronously.
|
|
24
|
+
*/
|
|
14
25
|
onError?: (err: unknown) => void;
|
|
26
|
+
/**
|
|
27
|
+
* Called when a cap-downgrade swaps the model. Useful for analytics.
|
|
28
|
+
* Defaults to a one-time console.warn per (userId, from, to).
|
|
29
|
+
*/
|
|
30
|
+
onDowngrade?: (info: {
|
|
31
|
+
userId: string;
|
|
32
|
+
from: string;
|
|
33
|
+
to: string;
|
|
34
|
+
}) => void;
|
|
15
35
|
}
|
|
16
36
|
interface ChatOptions {
|
|
17
37
|
model: string;
|
|
18
38
|
messages: Array<{
|
|
19
39
|
role: string;
|
|
20
|
-
content:
|
|
40
|
+
content: unknown;
|
|
21
41
|
}>;
|
|
22
42
|
userId?: string;
|
|
23
43
|
feature?: string;
|
|
24
44
|
plan?: string;
|
|
45
|
+
/** OpenAI streaming opt-in — set to true to get an AsyncIterable response. */
|
|
46
|
+
stream?: boolean;
|
|
25
47
|
[key: string]: unknown;
|
|
26
48
|
}
|
|
27
49
|
interface NormalizedUsage {
|
|
@@ -39,6 +61,7 @@ interface LogPayload {
|
|
|
39
61
|
latencyMs: number;
|
|
40
62
|
planName: string | null;
|
|
41
63
|
planRevenueUsd: number | null;
|
|
64
|
+
/** Kept for backward-compat with old servers; new servers ignore it. */
|
|
42
65
|
marginUsd: number | null;
|
|
43
66
|
timestamp: string;
|
|
44
67
|
}
|
|
@@ -60,11 +83,20 @@ interface CapCheckResult {
|
|
|
60
83
|
declare class Weckr {
|
|
61
84
|
private readonly apiKey;
|
|
62
85
|
private readonly plans;
|
|
63
|
-
private readonly
|
|
86
|
+
private readonly logger;
|
|
64
87
|
private readonly checkCap;
|
|
65
88
|
private readonly onError?;
|
|
89
|
+
private readonly onDowngrade;
|
|
90
|
+
private readonly downgradeSeen;
|
|
66
91
|
constructor(config: WeckrConfig);
|
|
67
92
|
chat<TClient, TResult = unknown>(client: TClient, options: ChatOptions): Promise<TResult>;
|
|
93
|
+
private tryLog;
|
|
94
|
+
/**
|
|
95
|
+
* Await all in-flight log POSTs. Call this before `process.exit()` /
|
|
96
|
+
* `Lambda return` / end of a short-lived CLI run, otherwise the daemon
|
|
97
|
+
* process is torn down before the POST hits the network.
|
|
98
|
+
*/
|
|
99
|
+
flush(timeoutMs?: number): Promise<void>;
|
|
68
100
|
}
|
|
69
101
|
|
|
70
102
|
interface ModelPricing {
|
|
@@ -73,6 +105,15 @@ interface ModelPricing {
|
|
|
73
105
|
outputPerMillion: number;
|
|
74
106
|
}
|
|
75
107
|
declare const PRICING: Record<string, ModelPricing>;
|
|
108
|
+
/**
|
|
109
|
+
* Resolve pricing for a model name, allowing dated variants.
|
|
110
|
+
*
|
|
111
|
+
* Real-world IDs are date-pinned (`gpt-4o-2024-08-06`, `claude-opus-4-20250514`,
|
|
112
|
+
* `claude-3-5-sonnet-latest`). Strict equality would silently log cost=0 for
|
|
113
|
+
* those — which neuters every cap. So we longest-prefix-match against PRICING:
|
|
114
|
+
* `claude-3-5-sonnet-20241022` resolves to `claude-3-5-sonnet`, not the
|
|
115
|
+
* shorter `claude-3` family.
|
|
116
|
+
*/
|
|
76
117
|
declare function resolvePricing(model: string): ModelPricing | null;
|
|
77
118
|
declare function calculateCost(model: string, inputTokens: number, outputTokens: number): {
|
|
78
119
|
costUsd: number;
|
|
@@ -87,7 +128,7 @@ declare function calculateCost(model: string, inputTokens: number, outputTokens:
|
|
|
87
128
|
* try {
|
|
88
129
|
* await wk.chat(openai, opts);
|
|
89
130
|
* } catch (err) {
|
|
90
|
-
* if (err
|
|
131
|
+
* if (isWeckrCapError(err)) {
|
|
91
132
|
* // show the user a friendly upgrade prompt
|
|
92
133
|
* }
|
|
93
134
|
* }
|
|
@@ -108,5 +149,19 @@ declare class WeckrCapError extends Error {
|
|
|
108
149
|
});
|
|
109
150
|
}
|
|
110
151
|
declare function isWeckrCapError(e: unknown): e is WeckrCapError;
|
|
152
|
+
/**
|
|
153
|
+
* Thrown when the SDK detects an UNRECOVERABLE config error — typo'd api key
|
|
154
|
+
* (401), revoked key (403), or plan-name passed to chat() that doesn't appear
|
|
155
|
+
* in the constructor's `plans` dict.
|
|
156
|
+
*
|
|
157
|
+
* These fail-CLOSED on purpose: silent fail-open would silently disable cap
|
|
158
|
+
* enforcement (security control) or silently poison dashboard data.
|
|
159
|
+
*/
|
|
160
|
+
declare class WeckrConfigError extends Error {
|
|
161
|
+
readonly name: "WeckrConfigError";
|
|
162
|
+
readonly code: 'invalid_api_key' | 'forbidden' | 'unknown_plan';
|
|
163
|
+
constructor(code: 'invalid_api_key' | 'forbidden' | 'unknown_plan', message: string);
|
|
164
|
+
}
|
|
165
|
+
declare function isWeckrConfigError(e: unknown): e is WeckrConfigError;
|
|
111
166
|
|
|
112
|
-
export { type CapCheckResult, type ChatOptions, type LogPayload, type NormalizedUsage, PRICING, type Provider, type ProviderAdapter, Weckr, WeckrCapError, type WeckrConfig, calculateCost, isWeckrCapError, resolvePricing };
|
|
167
|
+
export { type CapCheckResult, type ChatOptions, type LogPayload, type NormalizedUsage, PRICING, type Provider, type ProviderAdapter, Weckr, WeckrCapError, type WeckrConfig, WeckrConfigError, calculateCost, isWeckrCapError, isWeckrConfigError, resolvePricing };
|
package/dist/index.d.ts
CHANGED
|
@@ -1,6 +1,12 @@
|
|
|
1
|
-
|
|
1
|
+
/**
|
|
2
|
+
* 'unknown' is used when the SDK can't detect the provider via shape. We still
|
|
3
|
+
* log the row (so the dashboard sees something) but cost+downgrade lookup fall
|
|
4
|
+
* back to no-op behavior.
|
|
5
|
+
*/
|
|
6
|
+
type Provider = 'openai' | 'anthropic' | 'gemini' | 'unknown';
|
|
2
7
|
interface WeckrConfig {
|
|
3
8
|
apiKey: string;
|
|
9
|
+
/** Map of plan name -> monthly revenue per user. Required if you pass `plan` to chat(). */
|
|
4
10
|
plans?: Record<string, number>;
|
|
5
11
|
endpoint?: string;
|
|
6
12
|
/** Optional override for the cap-check endpoint. Derived from `endpoint` by default. */
|
|
@@ -11,17 +17,33 @@ interface WeckrConfig {
|
|
|
11
17
|
*/
|
|
12
18
|
disableCapCheck?: boolean;
|
|
13
19
|
fetch?: typeof fetch;
|
|
20
|
+
/**
|
|
21
|
+
* Async errors (cap-check network failure, log POST failure) are reported here.
|
|
22
|
+
* If absent, errors are silently swallowed. CRITICAL ones (401/403 on a
|
|
23
|
+
* misconfigured api key) still throw WeckrConfigError synchronously.
|
|
24
|
+
*/
|
|
14
25
|
onError?: (err: unknown) => void;
|
|
26
|
+
/**
|
|
27
|
+
* Called when a cap-downgrade swaps the model. Useful for analytics.
|
|
28
|
+
* Defaults to a one-time console.warn per (userId, from, to).
|
|
29
|
+
*/
|
|
30
|
+
onDowngrade?: (info: {
|
|
31
|
+
userId: string;
|
|
32
|
+
from: string;
|
|
33
|
+
to: string;
|
|
34
|
+
}) => void;
|
|
15
35
|
}
|
|
16
36
|
interface ChatOptions {
|
|
17
37
|
model: string;
|
|
18
38
|
messages: Array<{
|
|
19
39
|
role: string;
|
|
20
|
-
content:
|
|
40
|
+
content: unknown;
|
|
21
41
|
}>;
|
|
22
42
|
userId?: string;
|
|
23
43
|
feature?: string;
|
|
24
44
|
plan?: string;
|
|
45
|
+
/** OpenAI streaming opt-in — set to true to get an AsyncIterable response. */
|
|
46
|
+
stream?: boolean;
|
|
25
47
|
[key: string]: unknown;
|
|
26
48
|
}
|
|
27
49
|
interface NormalizedUsage {
|
|
@@ -39,6 +61,7 @@ interface LogPayload {
|
|
|
39
61
|
latencyMs: number;
|
|
40
62
|
planName: string | null;
|
|
41
63
|
planRevenueUsd: number | null;
|
|
64
|
+
/** Kept for backward-compat with old servers; new servers ignore it. */
|
|
42
65
|
marginUsd: number | null;
|
|
43
66
|
timestamp: string;
|
|
44
67
|
}
|
|
@@ -60,11 +83,20 @@ interface CapCheckResult {
|
|
|
60
83
|
declare class Weckr {
|
|
61
84
|
private readonly apiKey;
|
|
62
85
|
private readonly plans;
|
|
63
|
-
private readonly
|
|
86
|
+
private readonly logger;
|
|
64
87
|
private readonly checkCap;
|
|
65
88
|
private readonly onError?;
|
|
89
|
+
private readonly onDowngrade;
|
|
90
|
+
private readonly downgradeSeen;
|
|
66
91
|
constructor(config: WeckrConfig);
|
|
67
92
|
chat<TClient, TResult = unknown>(client: TClient, options: ChatOptions): Promise<TResult>;
|
|
93
|
+
private tryLog;
|
|
94
|
+
/**
|
|
95
|
+
* Await all in-flight log POSTs. Call this before `process.exit()` /
|
|
96
|
+
* `Lambda return` / end of a short-lived CLI run, otherwise the daemon
|
|
97
|
+
* process is torn down before the POST hits the network.
|
|
98
|
+
*/
|
|
99
|
+
flush(timeoutMs?: number): Promise<void>;
|
|
68
100
|
}
|
|
69
101
|
|
|
70
102
|
interface ModelPricing {
|
|
@@ -73,6 +105,15 @@ interface ModelPricing {
|
|
|
73
105
|
outputPerMillion: number;
|
|
74
106
|
}
|
|
75
107
|
declare const PRICING: Record<string, ModelPricing>;
|
|
108
|
+
/**
|
|
109
|
+
* Resolve pricing for a model name, allowing dated variants.
|
|
110
|
+
*
|
|
111
|
+
* Real-world IDs are date-pinned (`gpt-4o-2024-08-06`, `claude-opus-4-20250514`,
|
|
112
|
+
* `claude-3-5-sonnet-latest`). Strict equality would silently log cost=0 for
|
|
113
|
+
* those — which neuters every cap. So we longest-prefix-match against PRICING:
|
|
114
|
+
* `claude-3-5-sonnet-20241022` resolves to `claude-3-5-sonnet`, not the
|
|
115
|
+
* shorter `claude-3` family.
|
|
116
|
+
*/
|
|
76
117
|
declare function resolvePricing(model: string): ModelPricing | null;
|
|
77
118
|
declare function calculateCost(model: string, inputTokens: number, outputTokens: number): {
|
|
78
119
|
costUsd: number;
|
|
@@ -87,7 +128,7 @@ declare function calculateCost(model: string, inputTokens: number, outputTokens:
|
|
|
87
128
|
* try {
|
|
88
129
|
* await wk.chat(openai, opts);
|
|
89
130
|
* } catch (err) {
|
|
90
|
-
* if (err
|
|
131
|
+
* if (isWeckrCapError(err)) {
|
|
91
132
|
* // show the user a friendly upgrade prompt
|
|
92
133
|
* }
|
|
93
134
|
* }
|
|
@@ -108,5 +149,19 @@ declare class WeckrCapError extends Error {
|
|
|
108
149
|
});
|
|
109
150
|
}
|
|
110
151
|
declare function isWeckrCapError(e: unknown): e is WeckrCapError;
|
|
152
|
+
/**
|
|
153
|
+
* Thrown when the SDK detects an UNRECOVERABLE config error — typo'd api key
|
|
154
|
+
* (401), revoked key (403), or plan-name passed to chat() that doesn't appear
|
|
155
|
+
* in the constructor's `plans` dict.
|
|
156
|
+
*
|
|
157
|
+
* These fail-CLOSED on purpose: silent fail-open would silently disable cap
|
|
158
|
+
* enforcement (security control) or silently poison dashboard data.
|
|
159
|
+
*/
|
|
160
|
+
declare class WeckrConfigError extends Error {
|
|
161
|
+
readonly name: "WeckrConfigError";
|
|
162
|
+
readonly code: 'invalid_api_key' | 'forbidden' | 'unknown_plan';
|
|
163
|
+
constructor(code: 'invalid_api_key' | 'forbidden' | 'unknown_plan', message: string);
|
|
164
|
+
}
|
|
165
|
+
declare function isWeckrConfigError(e: unknown): e is WeckrConfigError;
|
|
111
166
|
|
|
112
|
-
export { type CapCheckResult, type ChatOptions, type LogPayload, type NormalizedUsage, PRICING, type Provider, type ProviderAdapter, Weckr, WeckrCapError, type WeckrConfig, calculateCost, isWeckrCapError, resolvePricing };
|
|
167
|
+
export { type CapCheckResult, type ChatOptions, type LogPayload, type NormalizedUsage, PRICING, type Provider, type ProviderAdapter, Weckr, WeckrCapError, type WeckrConfig, WeckrConfigError, calculateCost, isWeckrCapError, isWeckrConfigError, resolvePricing };
|
package/dist/index.js
CHANGED
|
@@ -23,30 +23,47 @@ __export(index_exports, {
|
|
|
23
23
|
PRICING: () => PRICING,
|
|
24
24
|
Weckr: () => Weckr,
|
|
25
25
|
WeckrCapError: () => WeckrCapError,
|
|
26
|
+
WeckrConfigError: () => WeckrConfigError,
|
|
26
27
|
calculateCost: () => calculateCost,
|
|
27
28
|
isWeckrCapError: () => isWeckrCapError,
|
|
29
|
+
isWeckrConfigError: () => isWeckrConfigError,
|
|
28
30
|
resolvePricing: () => resolvePricing
|
|
29
31
|
});
|
|
30
32
|
module.exports = __toCommonJS(index_exports);
|
|
31
33
|
|
|
32
34
|
// src/pricing.ts
|
|
33
35
|
var PRICING = {
|
|
36
|
+
// OpenAI
|
|
34
37
|
"gpt-4o": { provider: "openai", inputPerMillion: 2.5, outputPerMillion: 10 },
|
|
35
38
|
"gpt-4o-mini": { provider: "openai", inputPerMillion: 0.15, outputPerMillion: 0.6 },
|
|
39
|
+
"gpt-4-turbo": { provider: "openai", inputPerMillion: 10, outputPerMillion: 30 },
|
|
40
|
+
"gpt-4": { provider: "openai", inputPerMillion: 30, outputPerMillion: 60 },
|
|
36
41
|
"gpt-3.5-turbo": { provider: "openai", inputPerMillion: 0.5, outputPerMillion: 1.5 },
|
|
42
|
+
"o1-preview": { provider: "openai", inputPerMillion: 15, outputPerMillion: 60 },
|
|
43
|
+
"o1-mini": { provider: "openai", inputPerMillion: 3, outputPerMillion: 12 },
|
|
44
|
+
// Anthropic
|
|
37
45
|
"claude-opus-4": { provider: "anthropic", inputPerMillion: 15, outputPerMillion: 75 },
|
|
38
46
|
"claude-sonnet-4": { provider: "anthropic", inputPerMillion: 3, outputPerMillion: 15 },
|
|
39
47
|
"claude-haiku-4-5": { provider: "anthropic", inputPerMillion: 0.8, outputPerMillion: 4 },
|
|
48
|
+
"claude-3-5-sonnet": { provider: "anthropic", inputPerMillion: 3, outputPerMillion: 15 },
|
|
49
|
+
"claude-3-5-haiku": { provider: "anthropic", inputPerMillion: 0.8, outputPerMillion: 4 },
|
|
50
|
+
"claude-3-opus": { provider: "anthropic", inputPerMillion: 15, outputPerMillion: 75 },
|
|
51
|
+
// Gemini
|
|
52
|
+
"gemini-2.5-pro": { provider: "gemini", inputPerMillion: 1.25, outputPerMillion: 10 },
|
|
40
53
|
"gemini-2.5-flash": { provider: "gemini", inputPerMillion: 0.15, outputPerMillion: 0.6 },
|
|
41
|
-
"gemini-
|
|
54
|
+
"gemini-1.5-pro": { provider: "gemini", inputPerMillion: 1.25, outputPerMillion: 5 },
|
|
55
|
+
"gemini-1.5-flash": { provider: "gemini", inputPerMillion: 0.075, outputPerMillion: 0.3 }
|
|
42
56
|
};
|
|
43
57
|
function resolvePricing(model) {
|
|
44
58
|
if (PRICING[model]) return PRICING[model];
|
|
45
59
|
const lower = model.toLowerCase();
|
|
46
|
-
|
|
47
|
-
|
|
60
|
+
let best = null;
|
|
61
|
+
for (const [key, pricing] of Object.entries(PRICING)) {
|
|
62
|
+
if (lower.startsWith(key.toLowerCase())) {
|
|
63
|
+
if (!best || key.length > best.key.length) best = { key, pricing };
|
|
64
|
+
}
|
|
48
65
|
}
|
|
49
|
-
return null;
|
|
66
|
+
return best?.pricing ?? null;
|
|
50
67
|
}
|
|
51
68
|
function calculateCost(model, inputTokens, outputTokens) {
|
|
52
69
|
const pricing = resolvePricing(model);
|
|
@@ -62,14 +79,28 @@ function round6(n) {
|
|
|
62
79
|
var openaiAdapter = {
|
|
63
80
|
name: "openai",
|
|
64
81
|
matches(client) {
|
|
65
|
-
|
|
82
|
+
if (!isObject(client)) return false;
|
|
83
|
+
const chat = client.chat;
|
|
84
|
+
if (!isObject(chat)) return false;
|
|
85
|
+
const completions = chat.completions;
|
|
86
|
+
if (!isObject(completions)) return false;
|
|
87
|
+
return typeof completions.create === "function";
|
|
66
88
|
},
|
|
67
89
|
async call(client, options) {
|
|
90
|
+
if (options.stream === true) {
|
|
91
|
+
throw new Error(
|
|
92
|
+
"Weckr: stream:true is not supported by wk.chat() because token usage is not in stream responses by default. Either disable streaming, or call openai.chat.completions.create() directly outside wk.chat() (you lose cost tracking) and we will add proper streaming support in a future release."
|
|
93
|
+
);
|
|
94
|
+
}
|
|
68
95
|
const { userId, feature, plan, ...rest } = options;
|
|
96
|
+
void userId;
|
|
97
|
+
void feature;
|
|
98
|
+
void plan;
|
|
69
99
|
return client.chat.completions.create(rest);
|
|
70
100
|
},
|
|
71
101
|
extractUsage(result) {
|
|
72
|
-
const
|
|
102
|
+
const r = result;
|
|
103
|
+
const usage = r?.usage ?? {};
|
|
73
104
|
return {
|
|
74
105
|
inputTokens: toInt(usage.prompt_tokens ?? usage.input_tokens),
|
|
75
106
|
outputTokens: toInt(usage.completion_tokens ?? usage.output_tokens)
|
|
@@ -79,14 +110,21 @@ var openaiAdapter = {
|
|
|
79
110
|
var anthropicAdapter = {
|
|
80
111
|
name: "anthropic",
|
|
81
112
|
matches(client) {
|
|
82
|
-
|
|
113
|
+
if (!isObject(client)) return false;
|
|
114
|
+
const messages = client.messages;
|
|
115
|
+
if (!isObject(messages)) return false;
|
|
116
|
+
return typeof messages.create === "function";
|
|
83
117
|
},
|
|
84
118
|
async call(client, options) {
|
|
85
119
|
const { userId, feature, plan, ...rest } = options;
|
|
120
|
+
void userId;
|
|
121
|
+
void feature;
|
|
122
|
+
void plan;
|
|
86
123
|
return client.messages.create(rest);
|
|
87
124
|
},
|
|
88
125
|
extractUsage(result) {
|
|
89
|
-
const
|
|
126
|
+
const r = result;
|
|
127
|
+
const usage = r?.usage ?? {};
|
|
90
128
|
return {
|
|
91
129
|
inputTokens: toInt(usage.input_tokens),
|
|
92
130
|
outputTokens: toInt(usage.output_tokens)
|
|
@@ -96,18 +134,25 @@ var anthropicAdapter = {
|
|
|
96
134
|
var geminiAdapter = {
|
|
97
135
|
name: "gemini",
|
|
98
136
|
matches(client) {
|
|
99
|
-
|
|
137
|
+
if (!isObject(client)) return false;
|
|
138
|
+
const models = client.models;
|
|
139
|
+
if (!isObject(models)) return false;
|
|
140
|
+
return typeof models.generateContent === "function";
|
|
100
141
|
},
|
|
101
142
|
async call(client, options) {
|
|
102
143
|
const { userId, feature, plan, messages, ...rest } = options;
|
|
144
|
+
void userId;
|
|
145
|
+
void feature;
|
|
146
|
+
void plan;
|
|
103
147
|
const contents = (messages ?? []).map((m) => ({
|
|
104
148
|
role: m.role === "assistant" ? "model" : "user",
|
|
105
|
-
parts: [{ text: m.content }]
|
|
149
|
+
parts: [{ text: typeof m.content === "string" ? m.content : JSON.stringify(m.content) }]
|
|
106
150
|
}));
|
|
107
151
|
return client.models.generateContent({ ...rest, contents });
|
|
108
152
|
},
|
|
109
153
|
extractUsage(result) {
|
|
110
|
-
const
|
|
154
|
+
const r = result;
|
|
155
|
+
const meta = r?.usageMetadata ?? {};
|
|
111
156
|
return {
|
|
112
157
|
inputTokens: toInt(meta.promptTokenCount),
|
|
113
158
|
outputTokens: toInt(meta.candidatesTokenCount)
|
|
@@ -131,11 +176,15 @@ function toInt(v) {
|
|
|
131
176
|
|
|
132
177
|
// src/logger.ts
|
|
133
178
|
function createLogger(opts) {
|
|
134
|
-
const
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
179
|
+
const inflight = /* @__PURE__ */ new Set();
|
|
180
|
+
function log(payload) {
|
|
181
|
+
const f = opts.fetch ?? globalThis.fetch;
|
|
182
|
+
if (typeof f !== "function") {
|
|
183
|
+
opts.onError?.(
|
|
184
|
+
new Error("Weckr: global fetch is unavailable. Pass a fetch implementation via config.fetch.")
|
|
185
|
+
);
|
|
186
|
+
return;
|
|
187
|
+
}
|
|
139
188
|
queueMicrotask(() => {
|
|
140
189
|
let promise;
|
|
141
190
|
try {
|
|
@@ -152,63 +201,38 @@ function createLogger(opts) {
|
|
|
152
201
|
opts.onError?.(err);
|
|
153
202
|
return;
|
|
154
203
|
}
|
|
155
|
-
promise.then(async (res) => {
|
|
204
|
+
const tracked = promise.then(async (res) => {
|
|
156
205
|
if (!res.ok) {
|
|
157
206
|
const body = await res.text().catch(() => "");
|
|
158
207
|
opts.onError?.(
|
|
159
|
-
new Error(
|
|
208
|
+
new Error(
|
|
209
|
+
`Weckr log failed: ${res.status} ${res.statusText} ${body}. ` + (res.status === 401 || res.status === 403 ? `Verify the api key at https://app.useweckr.com/dashboard/settings.` : "")
|
|
210
|
+
)
|
|
160
211
|
);
|
|
161
212
|
}
|
|
162
213
|
}).catch((err) => {
|
|
163
214
|
opts.onError?.(err);
|
|
215
|
+
}).finally(() => {
|
|
216
|
+
inflight.delete(tracked);
|
|
164
217
|
});
|
|
218
|
+
inflight.add(tracked);
|
|
165
219
|
});
|
|
166
|
-
};
|
|
167
|
-
}
|
|
168
|
-
|
|
169
|
-
// src/cap-cache.ts
|
|
170
|
-
var TTL_MS = 6e4;
|
|
171
|
-
function createCapChecker(opts) {
|
|
172
|
-
const f = opts.fetch ?? globalThis.fetch;
|
|
173
|
-
const cache = /* @__PURE__ */ new Map();
|
|
174
|
-
function key(userId, planName) {
|
|
175
|
-
return `${userId}\0${planName}`;
|
|
176
220
|
}
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
const
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
return { allowed: true };
|
|
184
|
-
}
|
|
185
|
-
try {
|
|
186
|
-
const url = new URL(opts.endpoint);
|
|
187
|
-
url.searchParams.set("userId", userId);
|
|
188
|
-
url.searchParams.set("planName", planName);
|
|
189
|
-
if (model) url.searchParams.set("model", model);
|
|
190
|
-
const res = await f(url.toString(), {
|
|
191
|
-
method: "GET",
|
|
192
|
-
headers: { "x-api-key": opts.apiKey }
|
|
193
|
-
});
|
|
194
|
-
if (!res.ok) {
|
|
195
|
-
opts.onError?.(
|
|
196
|
-
new Error(`Weckr cap check failed: ${res.status} ${res.statusText}`)
|
|
197
|
-
);
|
|
198
|
-
return { allowed: true };
|
|
199
|
-
}
|
|
200
|
-
const json = await res.json();
|
|
201
|
-
cache.set(k, { result: json, expiresAt: now + TTL_MS });
|
|
202
|
-
return json;
|
|
203
|
-
} catch (err) {
|
|
204
|
-
opts.onError?.(err);
|
|
205
|
-
return { allowed: true };
|
|
221
|
+
async function flush(timeoutMs = 5e3) {
|
|
222
|
+
if (inflight.size === 0) return;
|
|
223
|
+
const all = Promise.allSettled(Array.from(inflight));
|
|
224
|
+
if (timeoutMs <= 0) {
|
|
225
|
+
await all;
|
|
226
|
+
return;
|
|
206
227
|
}
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
228
|
+
let timer;
|
|
229
|
+
const timeout = new Promise((resolve) => {
|
|
230
|
+
timer = setTimeout(() => resolve(), timeoutMs);
|
|
231
|
+
});
|
|
232
|
+
await Promise.race([all, timeout]);
|
|
233
|
+
if (timer) clearTimeout(timer);
|
|
234
|
+
}
|
|
235
|
+
return { log, flush };
|
|
212
236
|
}
|
|
213
237
|
|
|
214
238
|
// src/errors.ts
|
|
@@ -239,15 +263,94 @@ function capCheckToError(opts) {
|
|
|
239
263
|
cap: opts.result.cap
|
|
240
264
|
});
|
|
241
265
|
}
|
|
266
|
+
var WeckrConfigError = class extends Error {
|
|
267
|
+
name = "WeckrConfigError";
|
|
268
|
+
code;
|
|
269
|
+
constructor(code, message) {
|
|
270
|
+
super(message);
|
|
271
|
+
this.code = code;
|
|
272
|
+
}
|
|
273
|
+
};
|
|
274
|
+
function isWeckrConfigError(e) {
|
|
275
|
+
return e instanceof Error && e.name === "WeckrConfigError";
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
// src/cap-cache.ts
|
|
279
|
+
var TTL_MS = 6e4;
|
|
280
|
+
function cacheKey(userId, planName, model) {
|
|
281
|
+
return JSON.stringify([userId, planName, model ?? null]);
|
|
282
|
+
}
|
|
283
|
+
function createCapChecker(opts) {
|
|
284
|
+
const f = opts.fetch ?? globalThis.fetch;
|
|
285
|
+
const cache = /* @__PURE__ */ new Map();
|
|
286
|
+
const inflight = /* @__PURE__ */ new Map();
|
|
287
|
+
return async function checkCap(userId, planName, model) {
|
|
288
|
+
const k = cacheKey(userId, planName, model);
|
|
289
|
+
const now = Date.now();
|
|
290
|
+
const hit = cache.get(k);
|
|
291
|
+
if (hit && hit.expiresAt > now) return hit.result;
|
|
292
|
+
const pending = inflight.get(k);
|
|
293
|
+
if (pending) return pending;
|
|
294
|
+
if (typeof f !== "function") {
|
|
295
|
+
return { allowed: true };
|
|
296
|
+
}
|
|
297
|
+
const fetchPromise = (async () => {
|
|
298
|
+
try {
|
|
299
|
+
const url = new URL(opts.endpoint);
|
|
300
|
+
url.searchParams.set("userId", userId);
|
|
301
|
+
url.searchParams.set("planName", planName);
|
|
302
|
+
if (model) url.searchParams.set("model", model);
|
|
303
|
+
const res = await f(url.toString(), {
|
|
304
|
+
method: "GET",
|
|
305
|
+
headers: { "x-api-key": opts.apiKey }
|
|
306
|
+
});
|
|
307
|
+
if (res.status === 401 || res.status === 403) {
|
|
308
|
+
const body = await res.text().catch(() => "");
|
|
309
|
+
throw new WeckrConfigError(
|
|
310
|
+
res.status === 401 ? "invalid_api_key" : "forbidden",
|
|
311
|
+
`Weckr: cap-check rejected with ${res.status}. Verify the api key is correct, not revoked, and active in the dashboard. Server said: ${body || "(no body)"}`
|
|
312
|
+
);
|
|
313
|
+
}
|
|
314
|
+
if (!res.ok) {
|
|
315
|
+
opts.onError?.(
|
|
316
|
+
new Error(`Weckr cap check failed: ${res.status} ${res.statusText}`)
|
|
317
|
+
);
|
|
318
|
+
return { allowed: true };
|
|
319
|
+
}
|
|
320
|
+
const json = await res.json();
|
|
321
|
+
cache.set(k, { result: json, expiresAt: Date.now() + TTL_MS });
|
|
322
|
+
return json;
|
|
323
|
+
} catch (err) {
|
|
324
|
+
if (err instanceof WeckrConfigError) throw err;
|
|
325
|
+
opts.onError?.(err);
|
|
326
|
+
return { allowed: true };
|
|
327
|
+
} finally {
|
|
328
|
+
inflight.delete(k);
|
|
329
|
+
}
|
|
330
|
+
})();
|
|
331
|
+
inflight.set(k, fetchPromise);
|
|
332
|
+
return fetchPromise;
|
|
333
|
+
};
|
|
334
|
+
}
|
|
335
|
+
function deriveCheckEndpoint(logEndpoint) {
|
|
336
|
+
if (logEndpoint.endsWith("/log")) return logEndpoint.slice(0, -"/log".length) + "/check";
|
|
337
|
+
throw new WeckrConfigError(
|
|
338
|
+
"invalid_api_key",
|
|
339
|
+
// closest reusable code; semantically a config bug
|
|
340
|
+
`Weckr: cannot derive checkEndpoint from endpoint "${logEndpoint}" \u2014 it does not end in "/log". Pass an explicit \`checkEndpoint\` in the Weckr config.`
|
|
341
|
+
);
|
|
342
|
+
}
|
|
242
343
|
|
|
243
344
|
// src/weckr.ts
|
|
244
|
-
var DEFAULT_ENDPOINT = "https://useweckr.com/api/v1/log";
|
|
345
|
+
var DEFAULT_ENDPOINT = "https://app.useweckr.com/api/v1/log";
|
|
245
346
|
var Weckr = class {
|
|
246
347
|
apiKey;
|
|
247
348
|
plans;
|
|
248
|
-
|
|
349
|
+
logger;
|
|
249
350
|
checkCap;
|
|
250
351
|
onError;
|
|
352
|
+
onDowngrade;
|
|
353
|
+
downgradeSeen = /* @__PURE__ */ new Set();
|
|
251
354
|
constructor(config) {
|
|
252
355
|
if (!config?.apiKey) {
|
|
253
356
|
throw new Error("Weckr: apiKey is required.");
|
|
@@ -255,8 +358,9 @@ var Weckr = class {
|
|
|
255
358
|
this.apiKey = config.apiKey;
|
|
256
359
|
this.plans = config.plans ?? {};
|
|
257
360
|
this.onError = config.onError;
|
|
361
|
+
this.onDowngrade = config.onDowngrade ?? defaultDowngradeWarn.bind(this);
|
|
258
362
|
const logEndpoint = config.endpoint ?? DEFAULT_ENDPOINT;
|
|
259
|
-
this.
|
|
363
|
+
this.logger = createLogger({
|
|
260
364
|
apiKey: config.apiKey,
|
|
261
365
|
endpoint: logEndpoint,
|
|
262
366
|
fetch: config.fetch,
|
|
@@ -265,9 +369,10 @@ var Weckr = class {
|
|
|
265
369
|
if (config.disableCapCheck) {
|
|
266
370
|
this.checkCap = null;
|
|
267
371
|
} else {
|
|
372
|
+
const checkEndpoint = config.checkEndpoint ?? deriveCheckEndpoint(logEndpoint);
|
|
268
373
|
this.checkCap = createCapChecker({
|
|
269
374
|
apiKey: config.apiKey,
|
|
270
|
-
endpoint:
|
|
375
|
+
endpoint: checkEndpoint,
|
|
271
376
|
fetch: config.fetch,
|
|
272
377
|
onError: config.onError
|
|
273
378
|
});
|
|
@@ -280,16 +385,22 @@ var Weckr = class {
|
|
|
280
385
|
"Weckr: could not detect provider. Pass an OpenAI, Anthropic, or Gemini client instance."
|
|
281
386
|
);
|
|
282
387
|
}
|
|
388
|
+
if (options.plan != null && !Object.prototype.hasOwnProperty.call(this.plans, options.plan)) {
|
|
389
|
+
throw new WeckrConfigError(
|
|
390
|
+
"unknown_plan",
|
|
391
|
+
`Weckr: plan "${options.plan}" is not in the constructor's \`plans\` map. Add it as \`plans: { "${options.plan}": <monthly_usd> }\` when constructing Weckr.`
|
|
392
|
+
);
|
|
393
|
+
}
|
|
283
394
|
let effectiveOptions = options;
|
|
284
395
|
if (this.checkCap && options.userId && options.plan) {
|
|
285
396
|
const check = await this.checkCap(options.userId, options.plan, options.model);
|
|
286
397
|
if (!check.allowed) {
|
|
287
398
|
if (check.action === "downgrade" && check.alternativeModel) {
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
}
|
|
399
|
+
this.onDowngrade({
|
|
400
|
+
userId: options.userId,
|
|
401
|
+
from: options.model,
|
|
402
|
+
to: check.alternativeModel
|
|
403
|
+
});
|
|
293
404
|
effectiveOptions = { ...options, model: check.alternativeModel };
|
|
294
405
|
} else {
|
|
295
406
|
throw capCheckToError({
|
|
@@ -301,19 +412,40 @@ var Weckr = class {
|
|
|
301
412
|
}
|
|
302
413
|
}
|
|
303
414
|
const startedAt = nowMs();
|
|
304
|
-
|
|
415
|
+
let result;
|
|
416
|
+
try {
|
|
417
|
+
result = await adapter.call(client, effectiveOptions);
|
|
418
|
+
} catch (err) {
|
|
419
|
+
const latencyMs2 = Math.round(nowMs() - startedAt);
|
|
420
|
+
this.tryLog(
|
|
421
|
+
adapter.name,
|
|
422
|
+
effectiveOptions,
|
|
423
|
+
{ inputTokens: 0, outputTokens: 0 },
|
|
424
|
+
latencyMs2
|
|
425
|
+
);
|
|
426
|
+
throw err;
|
|
427
|
+
}
|
|
305
428
|
const latencyMs = Math.round(nowMs() - startedAt);
|
|
429
|
+
let usage = { inputTokens: 0, outputTokens: 0 };
|
|
306
430
|
try {
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
431
|
+
usage = adapter.extractUsage(result);
|
|
432
|
+
} catch (err) {
|
|
433
|
+
this.onError?.(err);
|
|
434
|
+
}
|
|
435
|
+
this.tryLog(adapter.name, effectiveOptions, usage, latencyMs);
|
|
436
|
+
return result;
|
|
437
|
+
}
|
|
438
|
+
tryLog(provider, options, usage, latencyMs) {
|
|
439
|
+
try {
|
|
440
|
+
const { costUsd } = calculateCost(options.model, usage.inputTokens, usage.outputTokens);
|
|
441
|
+
const planName = options.plan ?? null;
|
|
310
442
|
const planRevenueUsd = planName != null && Object.prototype.hasOwnProperty.call(this.plans, planName) ? this.plans[planName] : null;
|
|
311
|
-
const marginUsd = planRevenueUsd != null ?
|
|
443
|
+
const marginUsd = planRevenueUsd != null ? planRevenueUsd - costUsd : null;
|
|
312
444
|
const payload = {
|
|
313
|
-
userId:
|
|
314
|
-
feature:
|
|
315
|
-
model:
|
|
316
|
-
provider
|
|
445
|
+
userId: options.userId ?? null,
|
|
446
|
+
feature: options.feature ?? null,
|
|
447
|
+
model: options.model,
|
|
448
|
+
provider,
|
|
317
449
|
inputTokens: usage.inputTokens,
|
|
318
450
|
outputTokens: usage.outputTokens,
|
|
319
451
|
costUsd,
|
|
@@ -321,30 +453,48 @@ var Weckr = class {
|
|
|
321
453
|
planName,
|
|
322
454
|
planRevenueUsd,
|
|
323
455
|
marginUsd,
|
|
456
|
+
// sent for backward-compat; server ignores
|
|
324
457
|
timestamp: (/* @__PURE__ */ new Date()).toISOString()
|
|
325
458
|
};
|
|
326
|
-
this.log(payload);
|
|
459
|
+
this.logger.log(payload);
|
|
327
460
|
} catch (err) {
|
|
328
461
|
this.onError?.(err);
|
|
329
462
|
}
|
|
330
|
-
|
|
463
|
+
}
|
|
464
|
+
/**
|
|
465
|
+
* Await all in-flight log POSTs. Call this before `process.exit()` /
|
|
466
|
+
* `Lambda return` / end of a short-lived CLI run, otherwise the daemon
|
|
467
|
+
* process is torn down before the POST hits the network.
|
|
468
|
+
*/
|
|
469
|
+
flush(timeoutMs) {
|
|
470
|
+
return this.logger.flush(timeoutMs);
|
|
331
471
|
}
|
|
332
472
|
};
|
|
473
|
+
function defaultDowngradeWarn(info) {
|
|
474
|
+
const key = `${info.userId}:${info.from}>${info.to}`;
|
|
475
|
+
const seen = this.downgradeSeen;
|
|
476
|
+
if (seen.has(key)) return;
|
|
477
|
+
seen.add(key);
|
|
478
|
+
if (typeof console !== "undefined" && console.warn) {
|
|
479
|
+
console.warn(
|
|
480
|
+
`Weckr: downgrading ${info.userId} from ${info.from} to ${info.to} (cap reached). Subsequent downgrades for this user/model will be silent.`
|
|
481
|
+
);
|
|
482
|
+
}
|
|
483
|
+
}
|
|
333
484
|
function nowMs() {
|
|
334
485
|
if (typeof performance !== "undefined" && typeof performance.now === "function") {
|
|
335
486
|
return performance.now();
|
|
336
487
|
}
|
|
337
488
|
return Date.now();
|
|
338
489
|
}
|
|
339
|
-
function round2(n) {
|
|
340
|
-
return Math.round(n * 100) / 100;
|
|
341
|
-
}
|
|
342
490
|
// Annotate the CommonJS export names for ESM import in node:
|
|
343
491
|
0 && (module.exports = {
|
|
344
492
|
PRICING,
|
|
345
493
|
Weckr,
|
|
346
494
|
WeckrCapError,
|
|
495
|
+
WeckrConfigError,
|
|
347
496
|
calculateCost,
|
|
348
497
|
isWeckrCapError,
|
|
498
|
+
isWeckrConfigError,
|
|
349
499
|
resolvePricing
|
|
350
500
|
});
|
package/dist/index.mjs
CHANGED
|
@@ -1,21 +1,36 @@
|
|
|
1
1
|
// src/pricing.ts
|
|
2
2
|
var PRICING = {
|
|
3
|
+
// OpenAI
|
|
3
4
|
"gpt-4o": { provider: "openai", inputPerMillion: 2.5, outputPerMillion: 10 },
|
|
4
5
|
"gpt-4o-mini": { provider: "openai", inputPerMillion: 0.15, outputPerMillion: 0.6 },
|
|
6
|
+
"gpt-4-turbo": { provider: "openai", inputPerMillion: 10, outputPerMillion: 30 },
|
|
7
|
+
"gpt-4": { provider: "openai", inputPerMillion: 30, outputPerMillion: 60 },
|
|
5
8
|
"gpt-3.5-turbo": { provider: "openai", inputPerMillion: 0.5, outputPerMillion: 1.5 },
|
|
9
|
+
"o1-preview": { provider: "openai", inputPerMillion: 15, outputPerMillion: 60 },
|
|
10
|
+
"o1-mini": { provider: "openai", inputPerMillion: 3, outputPerMillion: 12 },
|
|
11
|
+
// Anthropic
|
|
6
12
|
"claude-opus-4": { provider: "anthropic", inputPerMillion: 15, outputPerMillion: 75 },
|
|
7
13
|
"claude-sonnet-4": { provider: "anthropic", inputPerMillion: 3, outputPerMillion: 15 },
|
|
8
14
|
"claude-haiku-4-5": { provider: "anthropic", inputPerMillion: 0.8, outputPerMillion: 4 },
|
|
15
|
+
"claude-3-5-sonnet": { provider: "anthropic", inputPerMillion: 3, outputPerMillion: 15 },
|
|
16
|
+
"claude-3-5-haiku": { provider: "anthropic", inputPerMillion: 0.8, outputPerMillion: 4 },
|
|
17
|
+
"claude-3-opus": { provider: "anthropic", inputPerMillion: 15, outputPerMillion: 75 },
|
|
18
|
+
// Gemini
|
|
19
|
+
"gemini-2.5-pro": { provider: "gemini", inputPerMillion: 1.25, outputPerMillion: 10 },
|
|
9
20
|
"gemini-2.5-flash": { provider: "gemini", inputPerMillion: 0.15, outputPerMillion: 0.6 },
|
|
10
|
-
"gemini-
|
|
21
|
+
"gemini-1.5-pro": { provider: "gemini", inputPerMillion: 1.25, outputPerMillion: 5 },
|
|
22
|
+
"gemini-1.5-flash": { provider: "gemini", inputPerMillion: 0.075, outputPerMillion: 0.3 }
|
|
11
23
|
};
|
|
12
24
|
function resolvePricing(model) {
|
|
13
25
|
if (PRICING[model]) return PRICING[model];
|
|
14
26
|
const lower = model.toLowerCase();
|
|
15
|
-
|
|
16
|
-
|
|
27
|
+
let best = null;
|
|
28
|
+
for (const [key, pricing] of Object.entries(PRICING)) {
|
|
29
|
+
if (lower.startsWith(key.toLowerCase())) {
|
|
30
|
+
if (!best || key.length > best.key.length) best = { key, pricing };
|
|
31
|
+
}
|
|
17
32
|
}
|
|
18
|
-
return null;
|
|
33
|
+
return best?.pricing ?? null;
|
|
19
34
|
}
|
|
20
35
|
function calculateCost(model, inputTokens, outputTokens) {
|
|
21
36
|
const pricing = resolvePricing(model);
|
|
@@ -31,14 +46,28 @@ function round6(n) {
|
|
|
31
46
|
var openaiAdapter = {
|
|
32
47
|
name: "openai",
|
|
33
48
|
matches(client) {
|
|
34
|
-
|
|
49
|
+
if (!isObject(client)) return false;
|
|
50
|
+
const chat = client.chat;
|
|
51
|
+
if (!isObject(chat)) return false;
|
|
52
|
+
const completions = chat.completions;
|
|
53
|
+
if (!isObject(completions)) return false;
|
|
54
|
+
return typeof completions.create === "function";
|
|
35
55
|
},
|
|
36
56
|
async call(client, options) {
|
|
57
|
+
if (options.stream === true) {
|
|
58
|
+
throw new Error(
|
|
59
|
+
"Weckr: stream:true is not supported by wk.chat() because token usage is not in stream responses by default. Either disable streaming, or call openai.chat.completions.create() directly outside wk.chat() (you lose cost tracking) and we will add proper streaming support in a future release."
|
|
60
|
+
);
|
|
61
|
+
}
|
|
37
62
|
const { userId, feature, plan, ...rest } = options;
|
|
63
|
+
void userId;
|
|
64
|
+
void feature;
|
|
65
|
+
void plan;
|
|
38
66
|
return client.chat.completions.create(rest);
|
|
39
67
|
},
|
|
40
68
|
extractUsage(result) {
|
|
41
|
-
const
|
|
69
|
+
const r = result;
|
|
70
|
+
const usage = r?.usage ?? {};
|
|
42
71
|
return {
|
|
43
72
|
inputTokens: toInt(usage.prompt_tokens ?? usage.input_tokens),
|
|
44
73
|
outputTokens: toInt(usage.completion_tokens ?? usage.output_tokens)
|
|
@@ -48,14 +77,21 @@ var openaiAdapter = {
|
|
|
48
77
|
var anthropicAdapter = {
|
|
49
78
|
name: "anthropic",
|
|
50
79
|
matches(client) {
|
|
51
|
-
|
|
80
|
+
if (!isObject(client)) return false;
|
|
81
|
+
const messages = client.messages;
|
|
82
|
+
if (!isObject(messages)) return false;
|
|
83
|
+
return typeof messages.create === "function";
|
|
52
84
|
},
|
|
53
85
|
async call(client, options) {
|
|
54
86
|
const { userId, feature, plan, ...rest } = options;
|
|
87
|
+
void userId;
|
|
88
|
+
void feature;
|
|
89
|
+
void plan;
|
|
55
90
|
return client.messages.create(rest);
|
|
56
91
|
},
|
|
57
92
|
extractUsage(result) {
|
|
58
|
-
const
|
|
93
|
+
const r = result;
|
|
94
|
+
const usage = r?.usage ?? {};
|
|
59
95
|
return {
|
|
60
96
|
inputTokens: toInt(usage.input_tokens),
|
|
61
97
|
outputTokens: toInt(usage.output_tokens)
|
|
@@ -65,18 +101,25 @@ var anthropicAdapter = {
|
|
|
65
101
|
var geminiAdapter = {
|
|
66
102
|
name: "gemini",
|
|
67
103
|
matches(client) {
|
|
68
|
-
|
|
104
|
+
if (!isObject(client)) return false;
|
|
105
|
+
const models = client.models;
|
|
106
|
+
if (!isObject(models)) return false;
|
|
107
|
+
return typeof models.generateContent === "function";
|
|
69
108
|
},
|
|
70
109
|
async call(client, options) {
|
|
71
110
|
const { userId, feature, plan, messages, ...rest } = options;
|
|
111
|
+
void userId;
|
|
112
|
+
void feature;
|
|
113
|
+
void plan;
|
|
72
114
|
const contents = (messages ?? []).map((m) => ({
|
|
73
115
|
role: m.role === "assistant" ? "model" : "user",
|
|
74
|
-
parts: [{ text: m.content }]
|
|
116
|
+
parts: [{ text: typeof m.content === "string" ? m.content : JSON.stringify(m.content) }]
|
|
75
117
|
}));
|
|
76
118
|
return client.models.generateContent({ ...rest, contents });
|
|
77
119
|
},
|
|
78
120
|
extractUsage(result) {
|
|
79
|
-
const
|
|
121
|
+
const r = result;
|
|
122
|
+
const meta = r?.usageMetadata ?? {};
|
|
80
123
|
return {
|
|
81
124
|
inputTokens: toInt(meta.promptTokenCount),
|
|
82
125
|
outputTokens: toInt(meta.candidatesTokenCount)
|
|
@@ -100,11 +143,15 @@ function toInt(v) {
|
|
|
100
143
|
|
|
101
144
|
// src/logger.ts
|
|
102
145
|
function createLogger(opts) {
|
|
103
|
-
const
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
146
|
+
const inflight = /* @__PURE__ */ new Set();
|
|
147
|
+
function log(payload) {
|
|
148
|
+
const f = opts.fetch ?? globalThis.fetch;
|
|
149
|
+
if (typeof f !== "function") {
|
|
150
|
+
opts.onError?.(
|
|
151
|
+
new Error("Weckr: global fetch is unavailable. Pass a fetch implementation via config.fetch.")
|
|
152
|
+
);
|
|
153
|
+
return;
|
|
154
|
+
}
|
|
108
155
|
queueMicrotask(() => {
|
|
109
156
|
let promise;
|
|
110
157
|
try {
|
|
@@ -121,63 +168,38 @@ function createLogger(opts) {
|
|
|
121
168
|
opts.onError?.(err);
|
|
122
169
|
return;
|
|
123
170
|
}
|
|
124
|
-
promise.then(async (res) => {
|
|
171
|
+
const tracked = promise.then(async (res) => {
|
|
125
172
|
if (!res.ok) {
|
|
126
173
|
const body = await res.text().catch(() => "");
|
|
127
174
|
opts.onError?.(
|
|
128
|
-
new Error(
|
|
175
|
+
new Error(
|
|
176
|
+
`Weckr log failed: ${res.status} ${res.statusText} ${body}. ` + (res.status === 401 || res.status === 403 ? `Verify the api key at https://app.useweckr.com/dashboard/settings.` : "")
|
|
177
|
+
)
|
|
129
178
|
);
|
|
130
179
|
}
|
|
131
180
|
}).catch((err) => {
|
|
132
181
|
opts.onError?.(err);
|
|
182
|
+
}).finally(() => {
|
|
183
|
+
inflight.delete(tracked);
|
|
133
184
|
});
|
|
185
|
+
inflight.add(tracked);
|
|
134
186
|
});
|
|
135
|
-
};
|
|
136
|
-
}
|
|
137
|
-
|
|
138
|
-
// src/cap-cache.ts
|
|
139
|
-
var TTL_MS = 6e4;
|
|
140
|
-
function createCapChecker(opts) {
|
|
141
|
-
const f = opts.fetch ?? globalThis.fetch;
|
|
142
|
-
const cache = /* @__PURE__ */ new Map();
|
|
143
|
-
function key(userId, planName) {
|
|
144
|
-
return `${userId}\0${planName}`;
|
|
145
187
|
}
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
const
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
return { allowed: true };
|
|
153
|
-
}
|
|
154
|
-
try {
|
|
155
|
-
const url = new URL(opts.endpoint);
|
|
156
|
-
url.searchParams.set("userId", userId);
|
|
157
|
-
url.searchParams.set("planName", planName);
|
|
158
|
-
if (model) url.searchParams.set("model", model);
|
|
159
|
-
const res = await f(url.toString(), {
|
|
160
|
-
method: "GET",
|
|
161
|
-
headers: { "x-api-key": opts.apiKey }
|
|
162
|
-
});
|
|
163
|
-
if (!res.ok) {
|
|
164
|
-
opts.onError?.(
|
|
165
|
-
new Error(`Weckr cap check failed: ${res.status} ${res.statusText}`)
|
|
166
|
-
);
|
|
167
|
-
return { allowed: true };
|
|
168
|
-
}
|
|
169
|
-
const json = await res.json();
|
|
170
|
-
cache.set(k, { result: json, expiresAt: now + TTL_MS });
|
|
171
|
-
return json;
|
|
172
|
-
} catch (err) {
|
|
173
|
-
opts.onError?.(err);
|
|
174
|
-
return { allowed: true };
|
|
188
|
+
async function flush(timeoutMs = 5e3) {
|
|
189
|
+
if (inflight.size === 0) return;
|
|
190
|
+
const all = Promise.allSettled(Array.from(inflight));
|
|
191
|
+
if (timeoutMs <= 0) {
|
|
192
|
+
await all;
|
|
193
|
+
return;
|
|
175
194
|
}
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
195
|
+
let timer;
|
|
196
|
+
const timeout = new Promise((resolve) => {
|
|
197
|
+
timer = setTimeout(() => resolve(), timeoutMs);
|
|
198
|
+
});
|
|
199
|
+
await Promise.race([all, timeout]);
|
|
200
|
+
if (timer) clearTimeout(timer);
|
|
201
|
+
}
|
|
202
|
+
return { log, flush };
|
|
181
203
|
}
|
|
182
204
|
|
|
183
205
|
// src/errors.ts
|
|
@@ -208,15 +230,94 @@ function capCheckToError(opts) {
|
|
|
208
230
|
cap: opts.result.cap
|
|
209
231
|
});
|
|
210
232
|
}
|
|
233
|
+
var WeckrConfigError = class extends Error {
|
|
234
|
+
name = "WeckrConfigError";
|
|
235
|
+
code;
|
|
236
|
+
constructor(code, message) {
|
|
237
|
+
super(message);
|
|
238
|
+
this.code = code;
|
|
239
|
+
}
|
|
240
|
+
};
|
|
241
|
+
function isWeckrConfigError(e) {
|
|
242
|
+
return e instanceof Error && e.name === "WeckrConfigError";
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
// src/cap-cache.ts
|
|
246
|
+
var TTL_MS = 6e4;
|
|
247
|
+
function cacheKey(userId, planName, model) {
|
|
248
|
+
return JSON.stringify([userId, planName, model ?? null]);
|
|
249
|
+
}
|
|
250
|
+
function createCapChecker(opts) {
|
|
251
|
+
const f = opts.fetch ?? globalThis.fetch;
|
|
252
|
+
const cache = /* @__PURE__ */ new Map();
|
|
253
|
+
const inflight = /* @__PURE__ */ new Map();
|
|
254
|
+
return async function checkCap(userId, planName, model) {
|
|
255
|
+
const k = cacheKey(userId, planName, model);
|
|
256
|
+
const now = Date.now();
|
|
257
|
+
const hit = cache.get(k);
|
|
258
|
+
if (hit && hit.expiresAt > now) return hit.result;
|
|
259
|
+
const pending = inflight.get(k);
|
|
260
|
+
if (pending) return pending;
|
|
261
|
+
if (typeof f !== "function") {
|
|
262
|
+
return { allowed: true };
|
|
263
|
+
}
|
|
264
|
+
const fetchPromise = (async () => {
|
|
265
|
+
try {
|
|
266
|
+
const url = new URL(opts.endpoint);
|
|
267
|
+
url.searchParams.set("userId", userId);
|
|
268
|
+
url.searchParams.set("planName", planName);
|
|
269
|
+
if (model) url.searchParams.set("model", model);
|
|
270
|
+
const res = await f(url.toString(), {
|
|
271
|
+
method: "GET",
|
|
272
|
+
headers: { "x-api-key": opts.apiKey }
|
|
273
|
+
});
|
|
274
|
+
if (res.status === 401 || res.status === 403) {
|
|
275
|
+
const body = await res.text().catch(() => "");
|
|
276
|
+
throw new WeckrConfigError(
|
|
277
|
+
res.status === 401 ? "invalid_api_key" : "forbidden",
|
|
278
|
+
`Weckr: cap-check rejected with ${res.status}. Verify the api key is correct, not revoked, and active in the dashboard. Server said: ${body || "(no body)"}`
|
|
279
|
+
);
|
|
280
|
+
}
|
|
281
|
+
if (!res.ok) {
|
|
282
|
+
opts.onError?.(
|
|
283
|
+
new Error(`Weckr cap check failed: ${res.status} ${res.statusText}`)
|
|
284
|
+
);
|
|
285
|
+
return { allowed: true };
|
|
286
|
+
}
|
|
287
|
+
const json = await res.json();
|
|
288
|
+
cache.set(k, { result: json, expiresAt: Date.now() + TTL_MS });
|
|
289
|
+
return json;
|
|
290
|
+
} catch (err) {
|
|
291
|
+
if (err instanceof WeckrConfigError) throw err;
|
|
292
|
+
opts.onError?.(err);
|
|
293
|
+
return { allowed: true };
|
|
294
|
+
} finally {
|
|
295
|
+
inflight.delete(k);
|
|
296
|
+
}
|
|
297
|
+
})();
|
|
298
|
+
inflight.set(k, fetchPromise);
|
|
299
|
+
return fetchPromise;
|
|
300
|
+
};
|
|
301
|
+
}
|
|
302
|
+
function deriveCheckEndpoint(logEndpoint) {
|
|
303
|
+
if (logEndpoint.endsWith("/log")) return logEndpoint.slice(0, -"/log".length) + "/check";
|
|
304
|
+
throw new WeckrConfigError(
|
|
305
|
+
"invalid_api_key",
|
|
306
|
+
// closest reusable code; semantically a config bug
|
|
307
|
+
`Weckr: cannot derive checkEndpoint from endpoint "${logEndpoint}" \u2014 it does not end in "/log". Pass an explicit \`checkEndpoint\` in the Weckr config.`
|
|
308
|
+
);
|
|
309
|
+
}
|
|
211
310
|
|
|
212
311
|
// src/weckr.ts
|
|
213
|
-
var DEFAULT_ENDPOINT = "https://useweckr.com/api/v1/log";
|
|
312
|
+
var DEFAULT_ENDPOINT = "https://app.useweckr.com/api/v1/log";
|
|
214
313
|
var Weckr = class {
|
|
215
314
|
apiKey;
|
|
216
315
|
plans;
|
|
217
|
-
|
|
316
|
+
logger;
|
|
218
317
|
checkCap;
|
|
219
318
|
onError;
|
|
319
|
+
onDowngrade;
|
|
320
|
+
downgradeSeen = /* @__PURE__ */ new Set();
|
|
220
321
|
constructor(config) {
|
|
221
322
|
if (!config?.apiKey) {
|
|
222
323
|
throw new Error("Weckr: apiKey is required.");
|
|
@@ -224,8 +325,9 @@ var Weckr = class {
|
|
|
224
325
|
this.apiKey = config.apiKey;
|
|
225
326
|
this.plans = config.plans ?? {};
|
|
226
327
|
this.onError = config.onError;
|
|
328
|
+
this.onDowngrade = config.onDowngrade ?? defaultDowngradeWarn.bind(this);
|
|
227
329
|
const logEndpoint = config.endpoint ?? DEFAULT_ENDPOINT;
|
|
228
|
-
this.
|
|
330
|
+
this.logger = createLogger({
|
|
229
331
|
apiKey: config.apiKey,
|
|
230
332
|
endpoint: logEndpoint,
|
|
231
333
|
fetch: config.fetch,
|
|
@@ -234,9 +336,10 @@ var Weckr = class {
|
|
|
234
336
|
if (config.disableCapCheck) {
|
|
235
337
|
this.checkCap = null;
|
|
236
338
|
} else {
|
|
339
|
+
const checkEndpoint = config.checkEndpoint ?? deriveCheckEndpoint(logEndpoint);
|
|
237
340
|
this.checkCap = createCapChecker({
|
|
238
341
|
apiKey: config.apiKey,
|
|
239
|
-
endpoint:
|
|
342
|
+
endpoint: checkEndpoint,
|
|
240
343
|
fetch: config.fetch,
|
|
241
344
|
onError: config.onError
|
|
242
345
|
});
|
|
@@ -249,16 +352,22 @@ var Weckr = class {
|
|
|
249
352
|
"Weckr: could not detect provider. Pass an OpenAI, Anthropic, or Gemini client instance."
|
|
250
353
|
);
|
|
251
354
|
}
|
|
355
|
+
if (options.plan != null && !Object.prototype.hasOwnProperty.call(this.plans, options.plan)) {
|
|
356
|
+
throw new WeckrConfigError(
|
|
357
|
+
"unknown_plan",
|
|
358
|
+
`Weckr: plan "${options.plan}" is not in the constructor's \`plans\` map. Add it as \`plans: { "${options.plan}": <monthly_usd> }\` when constructing Weckr.`
|
|
359
|
+
);
|
|
360
|
+
}
|
|
252
361
|
let effectiveOptions = options;
|
|
253
362
|
if (this.checkCap && options.userId && options.plan) {
|
|
254
363
|
const check = await this.checkCap(options.userId, options.plan, options.model);
|
|
255
364
|
if (!check.allowed) {
|
|
256
365
|
if (check.action === "downgrade" && check.alternativeModel) {
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
}
|
|
366
|
+
this.onDowngrade({
|
|
367
|
+
userId: options.userId,
|
|
368
|
+
from: options.model,
|
|
369
|
+
to: check.alternativeModel
|
|
370
|
+
});
|
|
262
371
|
effectiveOptions = { ...options, model: check.alternativeModel };
|
|
263
372
|
} else {
|
|
264
373
|
throw capCheckToError({
|
|
@@ -270,19 +379,40 @@ var Weckr = class {
|
|
|
270
379
|
}
|
|
271
380
|
}
|
|
272
381
|
const startedAt = nowMs();
|
|
273
|
-
|
|
382
|
+
let result;
|
|
383
|
+
try {
|
|
384
|
+
result = await adapter.call(client, effectiveOptions);
|
|
385
|
+
} catch (err) {
|
|
386
|
+
const latencyMs2 = Math.round(nowMs() - startedAt);
|
|
387
|
+
this.tryLog(
|
|
388
|
+
adapter.name,
|
|
389
|
+
effectiveOptions,
|
|
390
|
+
{ inputTokens: 0, outputTokens: 0 },
|
|
391
|
+
latencyMs2
|
|
392
|
+
);
|
|
393
|
+
throw err;
|
|
394
|
+
}
|
|
274
395
|
const latencyMs = Math.round(nowMs() - startedAt);
|
|
396
|
+
let usage = { inputTokens: 0, outputTokens: 0 };
|
|
275
397
|
try {
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
398
|
+
usage = adapter.extractUsage(result);
|
|
399
|
+
} catch (err) {
|
|
400
|
+
this.onError?.(err);
|
|
401
|
+
}
|
|
402
|
+
this.tryLog(adapter.name, effectiveOptions, usage, latencyMs);
|
|
403
|
+
return result;
|
|
404
|
+
}
|
|
405
|
+
tryLog(provider, options, usage, latencyMs) {
|
|
406
|
+
try {
|
|
407
|
+
const { costUsd } = calculateCost(options.model, usage.inputTokens, usage.outputTokens);
|
|
408
|
+
const planName = options.plan ?? null;
|
|
279
409
|
const planRevenueUsd = planName != null && Object.prototype.hasOwnProperty.call(this.plans, planName) ? this.plans[planName] : null;
|
|
280
|
-
const marginUsd = planRevenueUsd != null ?
|
|
410
|
+
const marginUsd = planRevenueUsd != null ? planRevenueUsd - costUsd : null;
|
|
281
411
|
const payload = {
|
|
282
|
-
userId:
|
|
283
|
-
feature:
|
|
284
|
-
model:
|
|
285
|
-
provider
|
|
412
|
+
userId: options.userId ?? null,
|
|
413
|
+
feature: options.feature ?? null,
|
|
414
|
+
model: options.model,
|
|
415
|
+
provider,
|
|
286
416
|
inputTokens: usage.inputTokens,
|
|
287
417
|
outputTokens: usage.outputTokens,
|
|
288
418
|
costUsd,
|
|
@@ -290,29 +420,47 @@ var Weckr = class {
|
|
|
290
420
|
planName,
|
|
291
421
|
planRevenueUsd,
|
|
292
422
|
marginUsd,
|
|
423
|
+
// sent for backward-compat; server ignores
|
|
293
424
|
timestamp: (/* @__PURE__ */ new Date()).toISOString()
|
|
294
425
|
};
|
|
295
|
-
this.log(payload);
|
|
426
|
+
this.logger.log(payload);
|
|
296
427
|
} catch (err) {
|
|
297
428
|
this.onError?.(err);
|
|
298
429
|
}
|
|
299
|
-
|
|
430
|
+
}
|
|
431
|
+
/**
|
|
432
|
+
* Await all in-flight log POSTs. Call this before `process.exit()` /
|
|
433
|
+
* `Lambda return` / end of a short-lived CLI run, otherwise the daemon
|
|
434
|
+
* process is torn down before the POST hits the network.
|
|
435
|
+
*/
|
|
436
|
+
flush(timeoutMs) {
|
|
437
|
+
return this.logger.flush(timeoutMs);
|
|
300
438
|
}
|
|
301
439
|
};
|
|
440
|
+
function defaultDowngradeWarn(info) {
|
|
441
|
+
const key = `${info.userId}:${info.from}>${info.to}`;
|
|
442
|
+
const seen = this.downgradeSeen;
|
|
443
|
+
if (seen.has(key)) return;
|
|
444
|
+
seen.add(key);
|
|
445
|
+
if (typeof console !== "undefined" && console.warn) {
|
|
446
|
+
console.warn(
|
|
447
|
+
`Weckr: downgrading ${info.userId} from ${info.from} to ${info.to} (cap reached). Subsequent downgrades for this user/model will be silent.`
|
|
448
|
+
);
|
|
449
|
+
}
|
|
450
|
+
}
|
|
302
451
|
function nowMs() {
|
|
303
452
|
if (typeof performance !== "undefined" && typeof performance.now === "function") {
|
|
304
453
|
return performance.now();
|
|
305
454
|
}
|
|
306
455
|
return Date.now();
|
|
307
456
|
}
|
|
308
|
-
function round2(n) {
|
|
309
|
-
return Math.round(n * 100) / 100;
|
|
310
|
-
}
|
|
311
457
|
export {
|
|
312
458
|
PRICING,
|
|
313
459
|
Weckr,
|
|
314
460
|
WeckrCapError,
|
|
461
|
+
WeckrConfigError,
|
|
315
462
|
calculateCost,
|
|
316
463
|
isWeckrCapError,
|
|
464
|
+
isWeckrConfigError,
|
|
317
465
|
resolvePricing
|
|
318
466
|
};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@weckr/sdk",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.3",
|
|
4
4
|
"description": "AI cost and margin intelligence for SaaS founders",
|
|
5
5
|
"main": "./dist/index.js",
|
|
6
6
|
"module": "./dist/index.mjs",
|
|
@@ -43,25 +43,24 @@
|
|
|
43
43
|
"url": "https://github.com/Ghiles3232/weckr"
|
|
44
44
|
},
|
|
45
45
|
"peerDependencies": {
|
|
46
|
-
"openai": ">=4.0.0"
|
|
46
|
+
"openai": ">=4.0.0",
|
|
47
|
+
"@anthropic-ai/sdk": ">=0.20.0",
|
|
48
|
+
"@google/genai": ">=0.4.0"
|
|
47
49
|
},
|
|
48
50
|
"peerDependenciesMeta": {
|
|
49
|
-
"openai": {
|
|
50
|
-
|
|
51
|
-
}
|
|
52
|
-
"@anthropic-ai/sdk": {
|
|
53
|
-
"optional": true
|
|
54
|
-
},
|
|
55
|
-
"@google/generative-ai": {
|
|
56
|
-
"optional": true
|
|
57
|
-
}
|
|
51
|
+
"openai": { "optional": true },
|
|
52
|
+
"@anthropic-ai/sdk": { "optional": true },
|
|
53
|
+
"@google/genai": { "optional": true }
|
|
58
54
|
},
|
|
59
55
|
"devDependencies": {
|
|
60
56
|
"@types/node": "^22.10.0",
|
|
61
57
|
"openai": "^4.77.0",
|
|
62
|
-
"tsup": "^8.
|
|
58
|
+
"tsup": "^8.5.1",
|
|
63
59
|
"tsx": "^4.19.2",
|
|
64
60
|
"typescript": "^5.7.2",
|
|
65
|
-
"vitest": "^
|
|
61
|
+
"vitest": "^4.1.8"
|
|
62
|
+
},
|
|
63
|
+
"overrides": {
|
|
64
|
+
"esbuild": "0.28.1"
|
|
66
65
|
}
|
|
67
66
|
}
|