@weckr/sdk 0.1.0 → 0.1.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.d.mts +48 -1
- package/dist/index.d.ts +48 -1
- package/dist/index.js +121 -8
- package/dist/index.mjs +119 -8
- package/package.json +10 -4
package/dist/index.d.mts
CHANGED
|
@@ -3,6 +3,13 @@ interface WeckrConfig {
|
|
|
3
3
|
apiKey: string;
|
|
4
4
|
plans?: Record<string, number>;
|
|
5
5
|
endpoint?: string;
|
|
6
|
+
/** Optional override for the cap-check endpoint. Derived from `endpoint` by default. */
|
|
7
|
+
checkEndpoint?: string;
|
|
8
|
+
/**
|
|
9
|
+
* Disable cap checking entirely. Off by default — the SDK will hit /api/v1/check
|
|
10
|
+
* before each LLM call (with a 60s per-user/plan cache).
|
|
11
|
+
*/
|
|
12
|
+
disableCapCheck?: boolean;
|
|
6
13
|
fetch?: typeof fetch;
|
|
7
14
|
onError?: (err: unknown) => void;
|
|
8
15
|
}
|
|
@@ -41,11 +48,21 @@ interface ProviderAdapter<TClient = unknown, TResult = unknown> {
|
|
|
41
48
|
call(client: TClient, options: ChatOptions): Promise<TResult>;
|
|
42
49
|
extractUsage(result: TResult): NormalizedUsage;
|
|
43
50
|
}
|
|
51
|
+
interface CapCheckResult {
|
|
52
|
+
allowed: boolean;
|
|
53
|
+
action?: 'block' | 'downgrade';
|
|
54
|
+
alternativeModel?: string;
|
|
55
|
+
remainingBudget?: number;
|
|
56
|
+
currentSpend?: number;
|
|
57
|
+
cap?: number;
|
|
58
|
+
}
|
|
44
59
|
|
|
45
60
|
declare class Weckr {
|
|
46
61
|
private readonly apiKey;
|
|
47
62
|
private readonly plans;
|
|
48
63
|
private readonly log;
|
|
64
|
+
private readonly checkCap;
|
|
65
|
+
private readonly onError?;
|
|
49
66
|
constructor(config: WeckrConfig);
|
|
50
67
|
chat<TClient, TResult = unknown>(client: TClient, options: ChatOptions): Promise<TResult>;
|
|
51
68
|
}
|
|
@@ -62,4 +79,34 @@ declare function calculateCost(model: string, inputTokens: number, outputTokens:
|
|
|
62
79
|
provider: Provider | null;
|
|
63
80
|
};
|
|
64
81
|
|
|
65
|
-
|
|
82
|
+
/**
|
|
83
|
+
* Thrown by `wk.chat(...)` when the configured spending cap has been hit and
|
|
84
|
+
* the cap's action is `"block"`. The LLM call is never made.
|
|
85
|
+
*
|
|
86
|
+
* ```ts
|
|
87
|
+
* try {
|
|
88
|
+
* await wk.chat(openai, opts);
|
|
89
|
+
* } catch (err) {
|
|
90
|
+
* if (err instanceof WeckrCapError) {
|
|
91
|
+
* // show the user a friendly upgrade prompt
|
|
92
|
+
* }
|
|
93
|
+
* }
|
|
94
|
+
* ```
|
|
95
|
+
*/
|
|
96
|
+
declare class WeckrCapError extends Error {
|
|
97
|
+
readonly name: "WeckrCapError";
|
|
98
|
+
readonly userId: string;
|
|
99
|
+
readonly planName: string;
|
|
100
|
+
readonly currentSpend?: number;
|
|
101
|
+
readonly cap?: number;
|
|
102
|
+
constructor(opts: {
|
|
103
|
+
userId: string;
|
|
104
|
+
planName: string;
|
|
105
|
+
currentSpend?: number;
|
|
106
|
+
cap?: number;
|
|
107
|
+
message?: string;
|
|
108
|
+
});
|
|
109
|
+
}
|
|
110
|
+
declare function isWeckrCapError(e: unknown): e is WeckrCapError;
|
|
111
|
+
|
|
112
|
+
export { type CapCheckResult, type ChatOptions, type LogPayload, type NormalizedUsage, PRICING, type Provider, type ProviderAdapter, Weckr, WeckrCapError, type WeckrConfig, calculateCost, isWeckrCapError, resolvePricing };
|
package/dist/index.d.ts
CHANGED
|
@@ -3,6 +3,13 @@ interface WeckrConfig {
|
|
|
3
3
|
apiKey: string;
|
|
4
4
|
plans?: Record<string, number>;
|
|
5
5
|
endpoint?: string;
|
|
6
|
+
/** Optional override for the cap-check endpoint. Derived from `endpoint` by default. */
|
|
7
|
+
checkEndpoint?: string;
|
|
8
|
+
/**
|
|
9
|
+
* Disable cap checking entirely. Off by default — the SDK will hit /api/v1/check
|
|
10
|
+
* before each LLM call (with a 60s per-user/plan cache).
|
|
11
|
+
*/
|
|
12
|
+
disableCapCheck?: boolean;
|
|
6
13
|
fetch?: typeof fetch;
|
|
7
14
|
onError?: (err: unknown) => void;
|
|
8
15
|
}
|
|
@@ -41,11 +48,21 @@ interface ProviderAdapter<TClient = unknown, TResult = unknown> {
|
|
|
41
48
|
call(client: TClient, options: ChatOptions): Promise<TResult>;
|
|
42
49
|
extractUsage(result: TResult): NormalizedUsage;
|
|
43
50
|
}
|
|
51
|
+
interface CapCheckResult {
|
|
52
|
+
allowed: boolean;
|
|
53
|
+
action?: 'block' | 'downgrade';
|
|
54
|
+
alternativeModel?: string;
|
|
55
|
+
remainingBudget?: number;
|
|
56
|
+
currentSpend?: number;
|
|
57
|
+
cap?: number;
|
|
58
|
+
}
|
|
44
59
|
|
|
45
60
|
declare class Weckr {
|
|
46
61
|
private readonly apiKey;
|
|
47
62
|
private readonly plans;
|
|
48
63
|
private readonly log;
|
|
64
|
+
private readonly checkCap;
|
|
65
|
+
private readonly onError?;
|
|
49
66
|
constructor(config: WeckrConfig);
|
|
50
67
|
chat<TClient, TResult = unknown>(client: TClient, options: ChatOptions): Promise<TResult>;
|
|
51
68
|
}
|
|
@@ -62,4 +79,34 @@ declare function calculateCost(model: string, inputTokens: number, outputTokens:
|
|
|
62
79
|
provider: Provider | null;
|
|
63
80
|
};
|
|
64
81
|
|
|
65
|
-
|
|
82
|
+
/**
|
|
83
|
+
* Thrown by `wk.chat(...)` when the configured spending cap has been hit and
|
|
84
|
+
* the cap's action is `"block"`. The LLM call is never made.
|
|
85
|
+
*
|
|
86
|
+
* ```ts
|
|
87
|
+
* try {
|
|
88
|
+
* await wk.chat(openai, opts);
|
|
89
|
+
* } catch (err) {
|
|
90
|
+
* if (err instanceof WeckrCapError) {
|
|
91
|
+
* // show the user a friendly upgrade prompt
|
|
92
|
+
* }
|
|
93
|
+
* }
|
|
94
|
+
* ```
|
|
95
|
+
*/
|
|
96
|
+
declare class WeckrCapError extends Error {
|
|
97
|
+
readonly name: "WeckrCapError";
|
|
98
|
+
readonly userId: string;
|
|
99
|
+
readonly planName: string;
|
|
100
|
+
readonly currentSpend?: number;
|
|
101
|
+
readonly cap?: number;
|
|
102
|
+
constructor(opts: {
|
|
103
|
+
userId: string;
|
|
104
|
+
planName: string;
|
|
105
|
+
currentSpend?: number;
|
|
106
|
+
cap?: number;
|
|
107
|
+
message?: string;
|
|
108
|
+
});
|
|
109
|
+
}
|
|
110
|
+
declare function isWeckrCapError(e: unknown): e is WeckrCapError;
|
|
111
|
+
|
|
112
|
+
export { type CapCheckResult, type ChatOptions, type LogPayload, type NormalizedUsage, PRICING, type Provider, type ProviderAdapter, Weckr, WeckrCapError, type WeckrConfig, calculateCost, isWeckrCapError, resolvePricing };
|
package/dist/index.js
CHANGED
|
@@ -22,7 +22,9 @@ var index_exports = {};
|
|
|
22
22
|
__export(index_exports, {
|
|
23
23
|
PRICING: () => PRICING,
|
|
24
24
|
Weckr: () => Weckr,
|
|
25
|
+
WeckrCapError: () => WeckrCapError,
|
|
25
26
|
calculateCost: () => calculateCost,
|
|
27
|
+
isWeckrCapError: () => isWeckrCapError,
|
|
26
28
|
resolvePricing: () => resolvePricing
|
|
27
29
|
});
|
|
28
30
|
module.exports = __toCommonJS(index_exports);
|
|
@@ -164,24 +166,112 @@ function createLogger(opts) {
|
|
|
164
166
|
};
|
|
165
167
|
}
|
|
166
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
|
+
}
|
|
177
|
+
return async function checkCap(userId, planName, model) {
|
|
178
|
+
const k = key(userId, planName);
|
|
179
|
+
const now = Date.now();
|
|
180
|
+
const hit = cache.get(k);
|
|
181
|
+
if (hit && hit.expiresAt > now) return hit.result;
|
|
182
|
+
if (typeof f !== "function") {
|
|
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 };
|
|
206
|
+
}
|
|
207
|
+
};
|
|
208
|
+
}
|
|
209
|
+
function deriveCheckEndpoint(logEndpoint) {
|
|
210
|
+
if (logEndpoint.endsWith("/log")) return logEndpoint.slice(0, -"/log".length) + "/check";
|
|
211
|
+
return logEndpoint.replace(/\/$/, "") + "/../check";
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
// src/errors.ts
|
|
215
|
+
var WeckrCapError = class extends Error {
|
|
216
|
+
name = "WeckrCapError";
|
|
217
|
+
userId;
|
|
218
|
+
planName;
|
|
219
|
+
currentSpend;
|
|
220
|
+
cap;
|
|
221
|
+
constructor(opts) {
|
|
222
|
+
super(
|
|
223
|
+
opts.message ?? `Weckr: spending cap reached for user ${opts.userId} on plan ${opts.planName}`
|
|
224
|
+
);
|
|
225
|
+
this.userId = opts.userId;
|
|
226
|
+
this.planName = opts.planName;
|
|
227
|
+
this.currentSpend = opts.currentSpend;
|
|
228
|
+
this.cap = opts.cap;
|
|
229
|
+
}
|
|
230
|
+
};
|
|
231
|
+
function isWeckrCapError(e) {
|
|
232
|
+
return e instanceof Error && e.name === "WeckrCapError";
|
|
233
|
+
}
|
|
234
|
+
function capCheckToError(opts) {
|
|
235
|
+
return new WeckrCapError({
|
|
236
|
+
userId: opts.userId,
|
|
237
|
+
planName: opts.planName,
|
|
238
|
+
currentSpend: opts.result.currentSpend,
|
|
239
|
+
cap: opts.result.cap
|
|
240
|
+
});
|
|
241
|
+
}
|
|
242
|
+
|
|
167
243
|
// src/weckr.ts
|
|
168
244
|
var DEFAULT_ENDPOINT = "https://useweckr.com/api/v1/log";
|
|
169
245
|
var Weckr = class {
|
|
170
246
|
apiKey;
|
|
171
247
|
plans;
|
|
172
248
|
log;
|
|
249
|
+
checkCap;
|
|
250
|
+
onError;
|
|
173
251
|
constructor(config) {
|
|
174
252
|
if (!config?.apiKey) {
|
|
175
253
|
throw new Error("Weckr: apiKey is required.");
|
|
176
254
|
}
|
|
177
255
|
this.apiKey = config.apiKey;
|
|
178
256
|
this.plans = config.plans ?? {};
|
|
257
|
+
this.onError = config.onError;
|
|
258
|
+
const logEndpoint = config.endpoint ?? DEFAULT_ENDPOINT;
|
|
179
259
|
this.log = createLogger({
|
|
180
260
|
apiKey: config.apiKey,
|
|
181
|
-
endpoint:
|
|
261
|
+
endpoint: logEndpoint,
|
|
182
262
|
fetch: config.fetch,
|
|
183
263
|
onError: config.onError
|
|
184
264
|
});
|
|
265
|
+
if (config.disableCapCheck) {
|
|
266
|
+
this.checkCap = null;
|
|
267
|
+
} else {
|
|
268
|
+
this.checkCap = createCapChecker({
|
|
269
|
+
apiKey: config.apiKey,
|
|
270
|
+
endpoint: config.checkEndpoint ?? deriveCheckEndpoint(logEndpoint),
|
|
271
|
+
fetch: config.fetch,
|
|
272
|
+
onError: config.onError
|
|
273
|
+
});
|
|
274
|
+
}
|
|
185
275
|
}
|
|
186
276
|
async chat(client, options) {
|
|
187
277
|
const adapter = detectAdapter(client);
|
|
@@ -190,19 +280,39 @@ var Weckr = class {
|
|
|
190
280
|
"Weckr: could not detect provider. Pass an OpenAI, Anthropic, or Gemini client instance."
|
|
191
281
|
);
|
|
192
282
|
}
|
|
283
|
+
let effectiveOptions = options;
|
|
284
|
+
if (this.checkCap && options.userId && options.plan) {
|
|
285
|
+
const check = await this.checkCap(options.userId, options.plan, options.model);
|
|
286
|
+
if (!check.allowed) {
|
|
287
|
+
if (check.action === "downgrade" && check.alternativeModel) {
|
|
288
|
+
if (typeof console !== "undefined" && console.warn) {
|
|
289
|
+
console.warn(
|
|
290
|
+
`Weckr: downgrading ${options.userId} from ${options.model} to ${check.alternativeModel} (cap reached).`
|
|
291
|
+
);
|
|
292
|
+
}
|
|
293
|
+
effectiveOptions = { ...options, model: check.alternativeModel };
|
|
294
|
+
} else {
|
|
295
|
+
throw capCheckToError({
|
|
296
|
+
userId: options.userId,
|
|
297
|
+
planName: options.plan,
|
|
298
|
+
result: check
|
|
299
|
+
});
|
|
300
|
+
}
|
|
301
|
+
}
|
|
302
|
+
}
|
|
193
303
|
const startedAt = nowMs();
|
|
194
|
-
const result = await adapter.call(client,
|
|
304
|
+
const result = await adapter.call(client, effectiveOptions);
|
|
195
305
|
const latencyMs = Math.round(nowMs() - startedAt);
|
|
196
306
|
try {
|
|
197
307
|
const usage = adapter.extractUsage(result);
|
|
198
|
-
const { costUsd } = calculateCost(
|
|
199
|
-
const planName =
|
|
308
|
+
const { costUsd } = calculateCost(effectiveOptions.model, usage.inputTokens, usage.outputTokens);
|
|
309
|
+
const planName = effectiveOptions.plan ?? null;
|
|
200
310
|
const planRevenueUsd = planName != null && Object.prototype.hasOwnProperty.call(this.plans, planName) ? this.plans[planName] : null;
|
|
201
311
|
const marginUsd = planRevenueUsd != null ? round2(planRevenueUsd - costUsd) : null;
|
|
202
312
|
const payload = {
|
|
203
|
-
userId:
|
|
204
|
-
feature:
|
|
205
|
-
model:
|
|
313
|
+
userId: effectiveOptions.userId ?? null,
|
|
314
|
+
feature: effectiveOptions.feature ?? null,
|
|
315
|
+
model: effectiveOptions.model,
|
|
206
316
|
provider: adapter.name,
|
|
207
317
|
inputTokens: usage.inputTokens,
|
|
208
318
|
outputTokens: usage.outputTokens,
|
|
@@ -214,7 +324,8 @@ var Weckr = class {
|
|
|
214
324
|
timestamp: (/* @__PURE__ */ new Date()).toISOString()
|
|
215
325
|
};
|
|
216
326
|
this.log(payload);
|
|
217
|
-
} catch {
|
|
327
|
+
} catch (err) {
|
|
328
|
+
this.onError?.(err);
|
|
218
329
|
}
|
|
219
330
|
return result;
|
|
220
331
|
}
|
|
@@ -232,6 +343,8 @@ function round2(n) {
|
|
|
232
343
|
0 && (module.exports = {
|
|
233
344
|
PRICING,
|
|
234
345
|
Weckr,
|
|
346
|
+
WeckrCapError,
|
|
235
347
|
calculateCost,
|
|
348
|
+
isWeckrCapError,
|
|
236
349
|
resolvePricing
|
|
237
350
|
});
|
package/dist/index.mjs
CHANGED
|
@@ -135,24 +135,112 @@ function createLogger(opts) {
|
|
|
135
135
|
};
|
|
136
136
|
}
|
|
137
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
|
+
}
|
|
146
|
+
return async function checkCap(userId, planName, model) {
|
|
147
|
+
const k = key(userId, planName);
|
|
148
|
+
const now = Date.now();
|
|
149
|
+
const hit = cache.get(k);
|
|
150
|
+
if (hit && hit.expiresAt > now) return hit.result;
|
|
151
|
+
if (typeof f !== "function") {
|
|
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 };
|
|
175
|
+
}
|
|
176
|
+
};
|
|
177
|
+
}
|
|
178
|
+
function deriveCheckEndpoint(logEndpoint) {
|
|
179
|
+
if (logEndpoint.endsWith("/log")) return logEndpoint.slice(0, -"/log".length) + "/check";
|
|
180
|
+
return logEndpoint.replace(/\/$/, "") + "/../check";
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
// src/errors.ts
|
|
184
|
+
var WeckrCapError = class extends Error {
|
|
185
|
+
name = "WeckrCapError";
|
|
186
|
+
userId;
|
|
187
|
+
planName;
|
|
188
|
+
currentSpend;
|
|
189
|
+
cap;
|
|
190
|
+
constructor(opts) {
|
|
191
|
+
super(
|
|
192
|
+
opts.message ?? `Weckr: spending cap reached for user ${opts.userId} on plan ${opts.planName}`
|
|
193
|
+
);
|
|
194
|
+
this.userId = opts.userId;
|
|
195
|
+
this.planName = opts.planName;
|
|
196
|
+
this.currentSpend = opts.currentSpend;
|
|
197
|
+
this.cap = opts.cap;
|
|
198
|
+
}
|
|
199
|
+
};
|
|
200
|
+
function isWeckrCapError(e) {
|
|
201
|
+
return e instanceof Error && e.name === "WeckrCapError";
|
|
202
|
+
}
|
|
203
|
+
function capCheckToError(opts) {
|
|
204
|
+
return new WeckrCapError({
|
|
205
|
+
userId: opts.userId,
|
|
206
|
+
planName: opts.planName,
|
|
207
|
+
currentSpend: opts.result.currentSpend,
|
|
208
|
+
cap: opts.result.cap
|
|
209
|
+
});
|
|
210
|
+
}
|
|
211
|
+
|
|
138
212
|
// src/weckr.ts
|
|
139
213
|
var DEFAULT_ENDPOINT = "https://useweckr.com/api/v1/log";
|
|
140
214
|
var Weckr = class {
|
|
141
215
|
apiKey;
|
|
142
216
|
plans;
|
|
143
217
|
log;
|
|
218
|
+
checkCap;
|
|
219
|
+
onError;
|
|
144
220
|
constructor(config) {
|
|
145
221
|
if (!config?.apiKey) {
|
|
146
222
|
throw new Error("Weckr: apiKey is required.");
|
|
147
223
|
}
|
|
148
224
|
this.apiKey = config.apiKey;
|
|
149
225
|
this.plans = config.plans ?? {};
|
|
226
|
+
this.onError = config.onError;
|
|
227
|
+
const logEndpoint = config.endpoint ?? DEFAULT_ENDPOINT;
|
|
150
228
|
this.log = createLogger({
|
|
151
229
|
apiKey: config.apiKey,
|
|
152
|
-
endpoint:
|
|
230
|
+
endpoint: logEndpoint,
|
|
153
231
|
fetch: config.fetch,
|
|
154
232
|
onError: config.onError
|
|
155
233
|
});
|
|
234
|
+
if (config.disableCapCheck) {
|
|
235
|
+
this.checkCap = null;
|
|
236
|
+
} else {
|
|
237
|
+
this.checkCap = createCapChecker({
|
|
238
|
+
apiKey: config.apiKey,
|
|
239
|
+
endpoint: config.checkEndpoint ?? deriveCheckEndpoint(logEndpoint),
|
|
240
|
+
fetch: config.fetch,
|
|
241
|
+
onError: config.onError
|
|
242
|
+
});
|
|
243
|
+
}
|
|
156
244
|
}
|
|
157
245
|
async chat(client, options) {
|
|
158
246
|
const adapter = detectAdapter(client);
|
|
@@ -161,19 +249,39 @@ var Weckr = class {
|
|
|
161
249
|
"Weckr: could not detect provider. Pass an OpenAI, Anthropic, or Gemini client instance."
|
|
162
250
|
);
|
|
163
251
|
}
|
|
252
|
+
let effectiveOptions = options;
|
|
253
|
+
if (this.checkCap && options.userId && options.plan) {
|
|
254
|
+
const check = await this.checkCap(options.userId, options.plan, options.model);
|
|
255
|
+
if (!check.allowed) {
|
|
256
|
+
if (check.action === "downgrade" && check.alternativeModel) {
|
|
257
|
+
if (typeof console !== "undefined" && console.warn) {
|
|
258
|
+
console.warn(
|
|
259
|
+
`Weckr: downgrading ${options.userId} from ${options.model} to ${check.alternativeModel} (cap reached).`
|
|
260
|
+
);
|
|
261
|
+
}
|
|
262
|
+
effectiveOptions = { ...options, model: check.alternativeModel };
|
|
263
|
+
} else {
|
|
264
|
+
throw capCheckToError({
|
|
265
|
+
userId: options.userId,
|
|
266
|
+
planName: options.plan,
|
|
267
|
+
result: check
|
|
268
|
+
});
|
|
269
|
+
}
|
|
270
|
+
}
|
|
271
|
+
}
|
|
164
272
|
const startedAt = nowMs();
|
|
165
|
-
const result = await adapter.call(client,
|
|
273
|
+
const result = await adapter.call(client, effectiveOptions);
|
|
166
274
|
const latencyMs = Math.round(nowMs() - startedAt);
|
|
167
275
|
try {
|
|
168
276
|
const usage = adapter.extractUsage(result);
|
|
169
|
-
const { costUsd } = calculateCost(
|
|
170
|
-
const planName =
|
|
277
|
+
const { costUsd } = calculateCost(effectiveOptions.model, usage.inputTokens, usage.outputTokens);
|
|
278
|
+
const planName = effectiveOptions.plan ?? null;
|
|
171
279
|
const planRevenueUsd = planName != null && Object.prototype.hasOwnProperty.call(this.plans, planName) ? this.plans[planName] : null;
|
|
172
280
|
const marginUsd = planRevenueUsd != null ? round2(planRevenueUsd - costUsd) : null;
|
|
173
281
|
const payload = {
|
|
174
|
-
userId:
|
|
175
|
-
feature:
|
|
176
|
-
model:
|
|
282
|
+
userId: effectiveOptions.userId ?? null,
|
|
283
|
+
feature: effectiveOptions.feature ?? null,
|
|
284
|
+
model: effectiveOptions.model,
|
|
177
285
|
provider: adapter.name,
|
|
178
286
|
inputTokens: usage.inputTokens,
|
|
179
287
|
outputTokens: usage.outputTokens,
|
|
@@ -185,7 +293,8 @@ var Weckr = class {
|
|
|
185
293
|
timestamp: (/* @__PURE__ */ new Date()).toISOString()
|
|
186
294
|
};
|
|
187
295
|
this.log(payload);
|
|
188
|
-
} catch {
|
|
296
|
+
} catch (err) {
|
|
297
|
+
this.onError?.(err);
|
|
189
298
|
}
|
|
190
299
|
return result;
|
|
191
300
|
}
|
|
@@ -202,6 +311,8 @@ function round2(n) {
|
|
|
202
311
|
export {
|
|
203
312
|
PRICING,
|
|
204
313
|
Weckr,
|
|
314
|
+
WeckrCapError,
|
|
205
315
|
calculateCost,
|
|
316
|
+
isWeckrCapError,
|
|
206
317
|
resolvePricing
|
|
207
318
|
};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@weckr/sdk",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.1",
|
|
4
4
|
"description": "AI cost and margin intelligence for SaaS founders",
|
|
5
5
|
"main": "./dist/index.js",
|
|
6
6
|
"module": "./dist/index.mjs",
|
|
@@ -46,9 +46,15 @@
|
|
|
46
46
|
"openai": ">=4.0.0"
|
|
47
47
|
},
|
|
48
48
|
"peerDependenciesMeta": {
|
|
49
|
-
"openai": {
|
|
50
|
-
|
|
51
|
-
|
|
49
|
+
"openai": {
|
|
50
|
+
"optional": true
|
|
51
|
+
},
|
|
52
|
+
"@anthropic-ai/sdk": {
|
|
53
|
+
"optional": true
|
|
54
|
+
},
|
|
55
|
+
"@google/generative-ai": {
|
|
56
|
+
"optional": true
|
|
57
|
+
}
|
|
52
58
|
},
|
|
53
59
|
"devDependencies": {
|
|
54
60
|
"@types/node": "^22.10.0",
|