@rawdash/connector-anthropic 0.27.0 → 0.28.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +7 -6
- package/dist/index.d.ts +78 -2
- package/dist/index.js +95 -59
- package/dist/index.js.map +1 -1
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -37,31 +37,32 @@ Authenticates with an Anthropic organization admin API key (sk-ant-admin-). Admi
|
|
|
37
37
|
- Endpoint: `GET /v1/organizations/usage_report/messages`
|
|
38
38
|
- Unit: tokens
|
|
39
39
|
- Granularity: daily
|
|
40
|
-
- Dimensions: `model`, `workspace_id`, `api_key_id`, `service_tier`, `context_window`, `inference_geo`
|
|
40
|
+
- Dimensions: `model`, `workspace_id`, `api_key_id`, `service_tier`, `context_window`, `inference_geo`, `account_id`, `service_account_id`
|
|
41
41
|
- Sample value is uncached_input_tokens. Cache-read and cache-creation token volumes are mirrored on their own metrics so a cache hit ratio can be computed at query time.
|
|
42
42
|
- **`anthropic_output_tokens`** _(metric)_ - Daily output tokens generated by the Anthropic Messages API, grouped by model and workspace.
|
|
43
43
|
- Endpoint: `GET /v1/organizations/usage_report/messages`
|
|
44
44
|
- Unit: tokens
|
|
45
45
|
- Granularity: daily
|
|
46
|
-
- Dimensions: `model`, `workspace_id`, `api_key_id`, `service_tier`, `context_window`, `inference_geo`
|
|
46
|
+
- Dimensions: `model`, `workspace_id`, `api_key_id`, `service_tier`, `context_window`, `inference_geo`, `account_id`, `service_account_id`
|
|
47
47
|
- Written alongside anthropic_input_tokens from the same usage_messages API call.
|
|
48
48
|
- **`anthropic_cache_read_tokens`** _(metric)_ - Daily input tokens read from the prompt cache, grouped by model and workspace.
|
|
49
49
|
- Endpoint: `GET /v1/organizations/usage_report/messages`
|
|
50
50
|
- Unit: tokens
|
|
51
51
|
- Granularity: daily
|
|
52
|
-
- Dimensions: `model`, `workspace_id`, `api_key_id`, `service_tier`, `context_window`, `inference_geo`
|
|
52
|
+
- Dimensions: `model`, `workspace_id`, `api_key_id`, `service_tier`, `context_window`, `inference_geo`, `account_id`, `service_account_id`
|
|
53
53
|
- Cache hits are charged at a fraction of the uncached rate, so this metric paired with anthropic_input_tokens gives the cache hit ratio.
|
|
54
54
|
- **`anthropic_cache_creation_tokens`** _(metric)_ - Daily input tokens written into the prompt cache (sum of the 1h and 5m ephemeral caches), grouped by model and workspace.
|
|
55
55
|
- Endpoint: `GET /v1/organizations/usage_report/messages`
|
|
56
56
|
- Unit: tokens
|
|
57
57
|
- Granularity: daily
|
|
58
|
-
- Dimensions: `model`, `workspace_id`, `api_key_id`, `service_tier`, `context_window`, `inference_geo`
|
|
59
|
-
-
|
|
58
|
+
- Dimensions: `model`, `workspace_id`, `api_key_id`, `service_tier`, `context_window`, `inference_geo`, `account_id`, `service_account_id`
|
|
59
|
+
- Measures: `ephemeral_1h_input_tokens`, `ephemeral_5m_input_tokens`
|
|
60
|
+
- The per-cache-bucket counts (ephemeral_1h_input_tokens, ephemeral_5m_input_tokens) are declared measures for finer-grained widgets.
|
|
60
61
|
- **`anthropic_web_search_requests`** _(metric)_ - Daily count of web-search tool requests executed server-side by Claude, grouped by model and workspace.
|
|
61
62
|
- Endpoint: `GET /v1/organizations/usage_report/messages`
|
|
62
63
|
- Unit: requests
|
|
63
64
|
- Granularity: daily
|
|
64
|
-
- Dimensions: `model`, `workspace_id`, `api_key_id`, `service_tier`, `context_window`, `inference_geo`
|
|
65
|
+
- Dimensions: `model`, `workspace_id`, `api_key_id`, `service_tier`, `context_window`, `inference_geo`, `account_id`, `service_account_id`
|
|
65
66
|
- Sourced from server_tool_use.web_search_requests on each usage bucket. Zero rows are still written so a "no usage today" widget renders correctly.
|
|
66
67
|
- **`anthropic_cost_usd`** _(metric)_ - Daily organization spend in USD, broken down by workspace and cost line item, pulled from the Anthropic Cost Report.
|
|
67
68
|
- Endpoint: `GET /v1/organizations/cost_report`
|
package/dist/index.d.ts
CHANGED
|
@@ -86,6 +86,12 @@ declare const anthropicResources: {
|
|
|
86
86
|
}, {
|
|
87
87
|
readonly name: "inference_geo";
|
|
88
88
|
readonly description: "Inference geo the request ran in (global, us, not_available), or null.";
|
|
89
|
+
}, {
|
|
90
|
+
readonly name: "account_id";
|
|
91
|
+
readonly description: "Account id the usage is attributed to (or null).";
|
|
92
|
+
}, {
|
|
93
|
+
readonly name: "service_account_id";
|
|
94
|
+
readonly description: "Service account id the usage is attributed to (or null).";
|
|
89
95
|
}];
|
|
90
96
|
readonly notes: "Sample value is uncached_input_tokens. Cache-read and cache-creation token volumes are mirrored on their own metrics so a cache hit ratio can be computed at query time.";
|
|
91
97
|
readonly responses: {
|
|
@@ -143,6 +149,12 @@ declare const anthropicResources: {
|
|
|
143
149
|
}, {
|
|
144
150
|
readonly name: "inference_geo";
|
|
145
151
|
readonly description: "Inference geo the request ran in (global, us, not_available), or null.";
|
|
152
|
+
}, {
|
|
153
|
+
readonly name: "account_id";
|
|
154
|
+
readonly description: "Account id the usage is attributed to (or null).";
|
|
155
|
+
}, {
|
|
156
|
+
readonly name: "service_account_id";
|
|
157
|
+
readonly description: "Service account id the usage is attributed to (or null).";
|
|
146
158
|
}];
|
|
147
159
|
readonly notes: "Written alongside anthropic_input_tokens from the same usage_messages API call.";
|
|
148
160
|
};
|
|
@@ -170,6 +182,12 @@ declare const anthropicResources: {
|
|
|
170
182
|
}, {
|
|
171
183
|
readonly name: "inference_geo";
|
|
172
184
|
readonly description: "Inference geo the request ran in (global, us, not_available), or null.";
|
|
185
|
+
}, {
|
|
186
|
+
readonly name: "account_id";
|
|
187
|
+
readonly description: "Account id the usage is attributed to (or null).";
|
|
188
|
+
}, {
|
|
189
|
+
readonly name: "service_account_id";
|
|
190
|
+
readonly description: "Service account id the usage is attributed to (or null).";
|
|
173
191
|
}];
|
|
174
192
|
readonly notes: "Cache hits are charged at a fraction of the uncached rate, so this metric paired with anthropic_input_tokens gives the cache hit ratio.";
|
|
175
193
|
};
|
|
@@ -197,8 +215,21 @@ declare const anthropicResources: {
|
|
|
197
215
|
}, {
|
|
198
216
|
readonly name: "inference_geo";
|
|
199
217
|
readonly description: "Inference geo the request ran in (global, us, not_available), or null.";
|
|
218
|
+
}, {
|
|
219
|
+
readonly name: "account_id";
|
|
220
|
+
readonly description: "Account id the usage is attributed to (or null).";
|
|
221
|
+
}, {
|
|
222
|
+
readonly name: "service_account_id";
|
|
223
|
+
readonly description: "Service account id the usage is attributed to (or null).";
|
|
224
|
+
}];
|
|
225
|
+
readonly measures: [{
|
|
226
|
+
readonly name: "ephemeral_1h_input_tokens";
|
|
227
|
+
readonly description: "Input tokens written into the 1-hour ephemeral prompt cache (a component of the sample value).";
|
|
228
|
+
}, {
|
|
229
|
+
readonly name: "ephemeral_5m_input_tokens";
|
|
230
|
+
readonly description: "Input tokens written into the 5-minute ephemeral prompt cache (a component of the sample value).";
|
|
200
231
|
}];
|
|
201
|
-
readonly notes: "The per-cache-bucket counts (ephemeral_1h_input_tokens, ephemeral_5m_input_tokens) are
|
|
232
|
+
readonly notes: "The per-cache-bucket counts (ephemeral_1h_input_tokens, ephemeral_5m_input_tokens) are declared measures for finer-grained widgets.";
|
|
202
233
|
};
|
|
203
234
|
readonly anthropic_web_search_requests: {
|
|
204
235
|
readonly shape: "metric";
|
|
@@ -224,6 +255,12 @@ declare const anthropicResources: {
|
|
|
224
255
|
}, {
|
|
225
256
|
readonly name: "inference_geo";
|
|
226
257
|
readonly description: "Inference geo the request ran in (global, us, not_available), or null.";
|
|
258
|
+
}, {
|
|
259
|
+
readonly name: "account_id";
|
|
260
|
+
readonly description: "Account id the usage is attributed to (or null).";
|
|
261
|
+
}, {
|
|
262
|
+
readonly name: "service_account_id";
|
|
263
|
+
readonly description: "Service account id the usage is attributed to (or null).";
|
|
227
264
|
}];
|
|
228
265
|
readonly notes: "Sourced from server_tool_use.web_search_requests on each usage bucket. Zero rows are still written so a \"no usage today\" widget renders correctly.";
|
|
229
266
|
};
|
|
@@ -299,6 +336,8 @@ declare const id = "anthropic";
|
|
|
299
336
|
interface UsageWindow {
|
|
300
337
|
startingAt: string;
|
|
301
338
|
endingAt: string;
|
|
339
|
+
startMs: number;
|
|
340
|
+
endMs: number;
|
|
302
341
|
}
|
|
303
342
|
declare function getUsageWindow(options: SyncOptions, lookbackDays: number, now?: number): UsageWindow;
|
|
304
343
|
declare function buildUsageSamples(buckets: readonly BucketPage<UsageResult>[]): {
|
|
@@ -336,6 +375,12 @@ declare class AnthropicConnector extends BaseConnector<AnthropicSettings, Anthro
|
|
|
336
375
|
}, {
|
|
337
376
|
readonly name: "inference_geo";
|
|
338
377
|
readonly description: "Inference geo the request ran in (global, us, not_available), or null.";
|
|
378
|
+
}, {
|
|
379
|
+
readonly name: "account_id";
|
|
380
|
+
readonly description: "Account id the usage is attributed to (or null).";
|
|
381
|
+
}, {
|
|
382
|
+
readonly name: "service_account_id";
|
|
383
|
+
readonly description: "Service account id the usage is attributed to (or null).";
|
|
339
384
|
}];
|
|
340
385
|
readonly notes: "Sample value is uncached_input_tokens. Cache-read and cache-creation token volumes are mirrored on their own metrics so a cache hit ratio can be computed at query time.";
|
|
341
386
|
readonly responses: {
|
|
@@ -393,6 +438,12 @@ declare class AnthropicConnector extends BaseConnector<AnthropicSettings, Anthro
|
|
|
393
438
|
}, {
|
|
394
439
|
readonly name: "inference_geo";
|
|
395
440
|
readonly description: "Inference geo the request ran in (global, us, not_available), or null.";
|
|
441
|
+
}, {
|
|
442
|
+
readonly name: "account_id";
|
|
443
|
+
readonly description: "Account id the usage is attributed to (or null).";
|
|
444
|
+
}, {
|
|
445
|
+
readonly name: "service_account_id";
|
|
446
|
+
readonly description: "Service account id the usage is attributed to (or null).";
|
|
396
447
|
}];
|
|
397
448
|
readonly notes: "Written alongside anthropic_input_tokens from the same usage_messages API call.";
|
|
398
449
|
};
|
|
@@ -420,6 +471,12 @@ declare class AnthropicConnector extends BaseConnector<AnthropicSettings, Anthro
|
|
|
420
471
|
}, {
|
|
421
472
|
readonly name: "inference_geo";
|
|
422
473
|
readonly description: "Inference geo the request ran in (global, us, not_available), or null.";
|
|
474
|
+
}, {
|
|
475
|
+
readonly name: "account_id";
|
|
476
|
+
readonly description: "Account id the usage is attributed to (or null).";
|
|
477
|
+
}, {
|
|
478
|
+
readonly name: "service_account_id";
|
|
479
|
+
readonly description: "Service account id the usage is attributed to (or null).";
|
|
423
480
|
}];
|
|
424
481
|
readonly notes: "Cache hits are charged at a fraction of the uncached rate, so this metric paired with anthropic_input_tokens gives the cache hit ratio.";
|
|
425
482
|
};
|
|
@@ -447,8 +504,21 @@ declare class AnthropicConnector extends BaseConnector<AnthropicSettings, Anthro
|
|
|
447
504
|
}, {
|
|
448
505
|
readonly name: "inference_geo";
|
|
449
506
|
readonly description: "Inference geo the request ran in (global, us, not_available), or null.";
|
|
507
|
+
}, {
|
|
508
|
+
readonly name: "account_id";
|
|
509
|
+
readonly description: "Account id the usage is attributed to (or null).";
|
|
510
|
+
}, {
|
|
511
|
+
readonly name: "service_account_id";
|
|
512
|
+
readonly description: "Service account id the usage is attributed to (or null).";
|
|
513
|
+
}];
|
|
514
|
+
readonly measures: [{
|
|
515
|
+
readonly name: "ephemeral_1h_input_tokens";
|
|
516
|
+
readonly description: "Input tokens written into the 1-hour ephemeral prompt cache (a component of the sample value).";
|
|
517
|
+
}, {
|
|
518
|
+
readonly name: "ephemeral_5m_input_tokens";
|
|
519
|
+
readonly description: "Input tokens written into the 5-minute ephemeral prompt cache (a component of the sample value).";
|
|
450
520
|
}];
|
|
451
|
-
readonly notes: "The per-cache-bucket counts (ephemeral_1h_input_tokens, ephemeral_5m_input_tokens) are
|
|
521
|
+
readonly notes: "The per-cache-bucket counts (ephemeral_1h_input_tokens, ephemeral_5m_input_tokens) are declared measures for finer-grained widgets.";
|
|
452
522
|
};
|
|
453
523
|
readonly anthropic_web_search_requests: {
|
|
454
524
|
readonly shape: "metric";
|
|
@@ -474,6 +544,12 @@ declare class AnthropicConnector extends BaseConnector<AnthropicSettings, Anthro
|
|
|
474
544
|
}, {
|
|
475
545
|
readonly name: "inference_geo";
|
|
476
546
|
readonly description: "Inference geo the request ran in (global, us, not_available), or null.";
|
|
547
|
+
}, {
|
|
548
|
+
readonly name: "account_id";
|
|
549
|
+
readonly description: "Account id the usage is attributed to (or null).";
|
|
550
|
+
}, {
|
|
551
|
+
readonly name: "service_account_id";
|
|
552
|
+
readonly description: "Service account id the usage is attributed to (or null).";
|
|
477
553
|
}];
|
|
478
554
|
readonly notes: "Sourced from server_tool_use.web_search_requests on each usage bucket. Zero rows are still written so a \"no usage today\" widget renders correctly.";
|
|
479
555
|
};
|
package/dist/index.js
CHANGED
|
@@ -33,6 +33,7 @@ import {
|
|
|
33
33
|
defineConnectorDoc,
|
|
34
34
|
defineResources,
|
|
35
35
|
makeChunkedCursorGuard,
|
|
36
|
+
metricSample,
|
|
36
37
|
schemasFromResources,
|
|
37
38
|
selectActivePhases
|
|
38
39
|
} from "@rawdash/core";
|
|
@@ -195,6 +196,14 @@ var USAGE_DIMENSIONS = [
|
|
|
195
196
|
{
|
|
196
197
|
name: "inference_geo",
|
|
197
198
|
description: "Inference geo the request ran in (global, us, not_available), or null."
|
|
199
|
+
},
|
|
200
|
+
{
|
|
201
|
+
name: "account_id",
|
|
202
|
+
description: "Account id the usage is attributed to (or null)."
|
|
203
|
+
},
|
|
204
|
+
{
|
|
205
|
+
name: "service_account_id",
|
|
206
|
+
description: "Service account id the usage is attributed to (or null)."
|
|
198
207
|
}
|
|
199
208
|
];
|
|
200
209
|
var COST_DIMENSIONS = [
|
|
@@ -267,7 +276,17 @@ var anthropicResources = defineResources({
|
|
|
267
276
|
unit: "tokens",
|
|
268
277
|
granularity: "daily",
|
|
269
278
|
dimensions: [...USAGE_DIMENSIONS],
|
|
270
|
-
|
|
279
|
+
measures: [
|
|
280
|
+
{
|
|
281
|
+
name: "ephemeral_1h_input_tokens",
|
|
282
|
+
description: "Input tokens written into the 1-hour ephemeral prompt cache (a component of the sample value)."
|
|
283
|
+
},
|
|
284
|
+
{
|
|
285
|
+
name: "ephemeral_5m_input_tokens",
|
|
286
|
+
description: "Input tokens written into the 5-minute ephemeral prompt cache (a component of the sample value)."
|
|
287
|
+
}
|
|
288
|
+
],
|
|
289
|
+
notes: "The per-cache-bucket counts (ephemeral_1h_input_tokens, ephemeral_5m_input_tokens) are declared measures for finer-grained widgets."
|
|
271
290
|
},
|
|
272
291
|
anthropic_web_search_requests: {
|
|
273
292
|
shape: "metric",
|
|
@@ -315,7 +334,9 @@ function getUsageWindow(options, lookbackDays, now = Date.now()) {
|
|
|
315
334
|
const startMs = endMs - days * MS_PER_DAY;
|
|
316
335
|
return {
|
|
317
336
|
startingAt: new Date(startMs).toISOString(),
|
|
318
|
-
endingAt: new Date(endMs).toISOString()
|
|
337
|
+
endingAt: new Date(endMs).toISOString(),
|
|
338
|
+
startMs,
|
|
339
|
+
endMs
|
|
319
340
|
};
|
|
320
341
|
}
|
|
321
342
|
function resourceToPhase(resource) {
|
|
@@ -365,40 +386,45 @@ function buildUsageSamples(buckets) {
|
|
|
365
386
|
for (const row of bucket.results) {
|
|
366
387
|
const common = usageDimensionAttributes(row);
|
|
367
388
|
const cacheCreation = row.cache_creation;
|
|
368
|
-
inputTokens.push(
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
389
|
+
inputTokens.push(
|
|
390
|
+
metricSample(anthropicResources, "anthropic_input_tokens", {
|
|
391
|
+
ts,
|
|
392
|
+
value: row.uncached_input_tokens,
|
|
393
|
+
attributes: { ...common }
|
|
394
|
+
})
|
|
395
|
+
);
|
|
396
|
+
outputTokens.push(
|
|
397
|
+
metricSample(anthropicResources, "anthropic_output_tokens", {
|
|
398
|
+
ts,
|
|
399
|
+
value: row.output_tokens,
|
|
400
|
+
attributes: { ...common }
|
|
401
|
+
})
|
|
402
|
+
);
|
|
403
|
+
cacheReadTokens.push(
|
|
404
|
+
metricSample(anthropicResources, "anthropic_cache_read_tokens", {
|
|
405
|
+
ts,
|
|
406
|
+
value: row.cache_read_input_tokens,
|
|
407
|
+
attributes: { ...common }
|
|
408
|
+
})
|
|
409
|
+
);
|
|
410
|
+
cacheCreationTokens.push(
|
|
411
|
+
metricSample(anthropicResources, "anthropic_cache_creation_tokens", {
|
|
412
|
+
ts,
|
|
413
|
+
value: cacheCreationTotal(row),
|
|
414
|
+
attributes: {
|
|
415
|
+
...common,
|
|
416
|
+
ephemeral_1h_input_tokens: cacheCreation?.ephemeral_1h_input_tokens ?? 0,
|
|
417
|
+
ephemeral_5m_input_tokens: cacheCreation?.ephemeral_5m_input_tokens ?? 0
|
|
418
|
+
}
|
|
419
|
+
})
|
|
420
|
+
);
|
|
421
|
+
webSearchRequests.push(
|
|
422
|
+
metricSample(anthropicResources, "anthropic_web_search_requests", {
|
|
423
|
+
ts,
|
|
424
|
+
value: row.server_tool_use?.web_search_requests ?? 0,
|
|
425
|
+
attributes: { ...common }
|
|
426
|
+
})
|
|
427
|
+
);
|
|
402
428
|
}
|
|
403
429
|
}
|
|
404
430
|
return {
|
|
@@ -419,21 +445,22 @@ function buildCostSamples(buckets) {
|
|
|
419
445
|
for (const row of bucket.results) {
|
|
420
446
|
const rawAmount = Number.parseFloat(row.amount);
|
|
421
447
|
const value = Number.isFinite(rawAmount) ? rawAmount / COST_AMOUNT_DIVISOR : 0;
|
|
422
|
-
samples.push(
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
448
|
+
samples.push(
|
|
449
|
+
metricSample(anthropicResources, "anthropic_cost_usd", {
|
|
450
|
+
ts,
|
|
451
|
+
value,
|
|
452
|
+
attributes: {
|
|
453
|
+
workspace_id: nullableString(row.workspace_id),
|
|
454
|
+
description: nullableString(row.description),
|
|
455
|
+
cost_type: nullableString(row.cost_type),
|
|
456
|
+
model: nullableString(row.model),
|
|
457
|
+
token_type: nullableString(row.token_type),
|
|
458
|
+
service_tier: nullableString(row.service_tier),
|
|
459
|
+
context_window: nullableString(row.context_window),
|
|
460
|
+
currency: row.currency
|
|
461
|
+
}
|
|
462
|
+
})
|
|
463
|
+
);
|
|
437
464
|
}
|
|
438
465
|
}
|
|
439
466
|
return samples;
|
|
@@ -551,7 +578,7 @@ var AnthropicConnector = class _AnthropicConnector extends BaseConnector {
|
|
|
551
578
|
}
|
|
552
579
|
pageUrl = nextUrl;
|
|
553
580
|
}
|
|
554
|
-
await this.writePhase(storage, phase, buckets);
|
|
581
|
+
await this.writePhase(storage, phase, buckets, window);
|
|
555
582
|
this.logger.info("resource done", {
|
|
556
583
|
resource: phase,
|
|
557
584
|
pages: pageCount,
|
|
@@ -581,30 +608,39 @@ var AnthropicConnector = class _AnthropicConnector extends BaseConnector {
|
|
|
581
608
|
);
|
|
582
609
|
}
|
|
583
610
|
}
|
|
584
|
-
async writePhase(storage, phase, buckets) {
|
|
611
|
+
async writePhase(storage, phase, buckets, window) {
|
|
612
|
+
const replaceWindow = { start: window.startMs, end: window.endMs };
|
|
585
613
|
switch (phase) {
|
|
586
614
|
case "usage_messages": {
|
|
587
615
|
const samples = buildUsageSamples(buckets);
|
|
588
616
|
await storage.metrics(samples.inputTokens, {
|
|
589
|
-
names: ["anthropic_input_tokens"]
|
|
617
|
+
names: ["anthropic_input_tokens"],
|
|
618
|
+
replaceWindow
|
|
590
619
|
});
|
|
591
620
|
await storage.metrics(samples.outputTokens, {
|
|
592
|
-
names: ["anthropic_output_tokens"]
|
|
621
|
+
names: ["anthropic_output_tokens"],
|
|
622
|
+
replaceWindow
|
|
593
623
|
});
|
|
594
624
|
await storage.metrics(samples.cacheReadTokens, {
|
|
595
|
-
names: ["anthropic_cache_read_tokens"]
|
|
625
|
+
names: ["anthropic_cache_read_tokens"],
|
|
626
|
+
replaceWindow
|
|
596
627
|
});
|
|
597
628
|
await storage.metrics(samples.cacheCreationTokens, {
|
|
598
|
-
names: ["anthropic_cache_creation_tokens"]
|
|
629
|
+
names: ["anthropic_cache_creation_tokens"],
|
|
630
|
+
replaceWindow
|
|
599
631
|
});
|
|
600
632
|
await storage.metrics(samples.webSearchRequests, {
|
|
601
|
-
names: ["anthropic_web_search_requests"]
|
|
633
|
+
names: ["anthropic_web_search_requests"],
|
|
634
|
+
replaceWindow
|
|
602
635
|
});
|
|
603
636
|
return;
|
|
604
637
|
}
|
|
605
638
|
case "cost_report": {
|
|
606
639
|
const samples = buildCostSamples(buckets);
|
|
607
|
-
await storage.metrics(samples, {
|
|
640
|
+
await storage.metrics(samples, {
|
|
641
|
+
names: ["anthropic_cost_usd"],
|
|
642
|
+
replaceWindow
|
|
643
|
+
});
|
|
608
644
|
return;
|
|
609
645
|
}
|
|
610
646
|
}
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../../connector-shared/src/errors.ts","../../../connector-shared/src/retry.ts","../../../connector-shared/src/version.ts","../../../connector-shared/src/request.ts","../../../connector-shared/src/rate-limit.ts","../../../connector-shared/src/map-concurrent.ts","../../../connector-shared/src/sanitize.ts","../../../connector-shared/src/epoch.ts","../../../connector-shared/src/pagination.ts","../../../connector-shared/src/logger.ts","../src/anthropic.ts","../src/index.ts"],"sourcesContent":["import type { HttpResponse } from './types';\n\nexport type HttpErrorKind =\n | 'transient'\n | 'rate_limit'\n | 'auth'\n | 'upstream_bug'\n | 'client_bug';\n\nexport abstract class HttpClientError extends Error {\n abstract readonly kind: HttpErrorKind;\n readonly response?: HttpResponse;\n\n constructor(message: string, response?: HttpResponse) {\n super(message);\n this.name = new.target.name;\n this.response = response;\n }\n}\n\nexport class TransientError extends HttpClientError {\n readonly kind = 'transient' as const;\n}\n\nexport class RateLimitError extends HttpClientError {\n readonly kind = 'rate_limit' as const;\n readonly retryAfter?: Date;\n\n constructor(message: string, response?: HttpResponse, retryAfter?: Date) {\n super(message, response);\n this.retryAfter = retryAfter;\n }\n}\n\nexport class AuthError extends HttpClientError {\n readonly kind = 'auth' as const;\n}\n\nexport class UpstreamBugError extends HttpClientError {\n readonly kind = 'upstream_bug' as const;\n}\n\nexport class ClientBugError extends HttpClientError {\n readonly kind = 'client_bug' as const;\n}\n\nexport function classifyStatus(status: number): HttpErrorKind {\n if (status === 429) {\n return 'rate_limit';\n }\n if (status === 401 || status === 403) {\n return 'auth';\n }\n if (status === 408) {\n return 'transient';\n }\n if (status >= 500) {\n return 'upstream_bug';\n }\n if (status >= 400) {\n return 'client_bug';\n }\n return 'client_bug';\n}\n\nexport function errorForStatus(\n message: string,\n response: HttpResponse,\n retryAfter?: Date,\n): HttpClientError {\n const kind = classifyStatus(response.status);\n switch (kind) {\n case 'rate_limit':\n return new RateLimitError(message, response, retryAfter);\n case 'auth':\n return new AuthError(message, response);\n case 'transient':\n return new TransientError(message, response);\n case 'upstream_bug':\n return new UpstreamBugError(message, response);\n case 'client_bug':\n return new ClientBugError(message, response);\n }\n}\n","import { HttpClientError, RateLimitError, TransientError } from './errors';\n\nexport interface RetryPolicy {\n maxAttempts?: number;\n initialDelayMs?: number;\n maxDelayMs?: number;\n retryOn?: (status: number | null, err?: Error) => boolean;\n}\n\nexport const defaultRetryOn = (status: number | null, err?: Error): boolean => {\n if (err instanceof RateLimitError) {\n return true;\n }\n if (err instanceof TransientError) {\n return true;\n }\n if (status === null) {\n return err instanceof Error && !(err instanceof HttpClientError);\n }\n if (status === 408 || status === 429) {\n return true;\n }\n if (status >= 500) {\n return true;\n }\n return false;\n};\n\nexport function backoffDelayMs(\n attempt: number,\n policy: Required<Pick<RetryPolicy, 'initialDelayMs' | 'maxDelayMs'>>,\n): number {\n const base = policy.initialDelayMs * 2 ** attempt;\n const jitter = base * 0.25 * Math.random();\n return Math.min(base + jitter, policy.maxDelayMs);\n}\n\nexport function parseRetryAfter(\n headerValue: string | null,\n now: Date = new Date(),\n): Date | undefined {\n if (!headerValue) {\n return undefined;\n }\n const trimmed = headerValue.trim();\n if (/^\\d+$/.test(trimmed)) {\n return new Date(now.getTime() + Number(trimmed) * 1000);\n }\n const parsed = Date.parse(trimmed);\n if (Number.isNaN(parsed)) {\n return undefined;\n }\n return new Date(parsed);\n}\n\nexport function sleep(ms: number, signal?: AbortSignal): Promise<void> {\n if (signal?.aborted) {\n return Promise.reject(signal.reason ?? new Error('Aborted'));\n }\n return new Promise<void>((resolve, reject) => {\n const onAbort = () => {\n clearTimeout(timer);\n reject(signal!.reason ?? new Error('Aborted'));\n };\n const timer = setTimeout(() => {\n signal?.removeEventListener('abort', onAbort);\n resolve();\n }, ms);\n signal?.addEventListener('abort', onAbort, { once: true });\n });\n}\n","export const HTTP_CLIENT_VERSION = '0.0.0';\n\nexport const DEFAULT_USER_AGENT = `rawdash-connector/${HTTP_CLIENT_VERSION} (+https://rawdash.dev)`;\n\nexport function connectorUserAgent(connectorId: string): string {\n return `rawdash-connector-${connectorId}/${HTTP_CLIENT_VERSION} (+https://rawdash.dev)`;\n}\n","import {\n AuthError,\n ClientBugError,\n HttpClientError,\n RateLimitError,\n TransientError,\n UpstreamBugError,\n errorForStatus,\n} from './errors';\nimport { defaultRetryOn, parseRetryAfter, sleep } from './retry';\nimport type { FetchLike, HttpMethod, HttpRequest, HttpResponse } from './types';\nimport { DEFAULT_USER_AGENT } from './version';\n\nconst DEFAULT_TIMEOUT_MS = 10_000;\nconst DEFAULT_MAX_ATTEMPTS = 3;\nconst DEFAULT_INITIAL_DELAY_MS = 1000;\nconst DEFAULT_MAX_DELAY_MS = 60_000;\nconst OBSERVER_TIMEOUT_MS = 250;\n\nexport interface RequestObservation {\n url: string;\n method: HttpMethod;\n status: number;\n resource: string;\n requestId: string;\n body: unknown;\n}\n\nexport type RequestObserver = (\n event: RequestObservation,\n) => void | Promise<void>;\n\nexport interface RequestOptions {\n fetch?: FetchLike;\n observer?: RequestObserver;\n resource: string;\n requestId?: string;\n}\n\nasync function notifyObserver(\n observer: RequestObserver,\n event: RequestObservation,\n): Promise<void> {\n let result: void | Promise<void>;\n try {\n result = observer(event);\n } catch (err) {\n console.warn('[connector-shared] request observer threw:', err);\n return;\n }\n if (!(result instanceof Promise)) {\n return;\n }\n const guarded = result.catch((err) => {\n console.warn('[connector-shared] request observer rejected:', err);\n });\n let timer: ReturnType<typeof setTimeout> | undefined;\n const timeout = new Promise<void>((resolve) => {\n timer = setTimeout(resolve, OBSERVER_TIMEOUT_MS);\n });\n try {\n await Promise.race([guarded, timeout]);\n } finally {\n if (timer) {\n clearTimeout(timer);\n }\n }\n}\n\nfunction newRequestId(): string {\n const c = (globalThis as { crypto?: { randomUUID?: () => string } }).crypto;\n if (c?.randomUUID) {\n return c.randomUUID();\n }\n return `${Date.now().toString(36)}-${Math.random().toString(36).slice(2, 10)}`;\n}\n\nfunction mergeHeaders(\n defaults: Record<string, string>,\n overrides: Record<string, string> | undefined,\n): Record<string, string> {\n const merged: Record<string, string> = {};\n for (const [k, v] of Object.entries(defaults)) {\n merged[k.toLowerCase()] = v;\n }\n if (overrides) {\n for (const [k, v] of Object.entries(overrides)) {\n merged[k.toLowerCase()] = v;\n }\n }\n return merged;\n}\n\nfunction linkTimeoutSignal(\n parent: AbortSignal | undefined,\n timeoutMs: number,\n): { signal: AbortSignal; cancel: () => void } {\n const controller = new AbortController();\n const onParentAbort = () => {\n controller.abort(parent?.reason);\n };\n if (parent) {\n if (parent.aborted) {\n controller.abort(parent.reason);\n } else {\n parent.addEventListener('abort', onParentAbort, { once: true });\n }\n }\n const timer = setTimeout(() => {\n controller.abort(new Error(`Request timed out after ${timeoutMs}ms`));\n }, timeoutMs);\n return {\n signal: controller.signal,\n cancel: () => {\n clearTimeout(timer);\n if (parent) {\n parent.removeEventListener('abort', onParentAbort);\n }\n },\n };\n}\n\nasync function readBody(res: Response, parseJson: boolean): Promise<unknown> {\n if (res.status === 204 || res.status === 205) {\n return null;\n }\n const contentType = res.headers.get('content-type') ?? '';\n if (parseJson && contentType.includes('application/json')) {\n const text = await res.text();\n if (text.length === 0) {\n return null;\n }\n return JSON.parse(text);\n }\n return res.text();\n}\n\nexport async function request<T = unknown>(\n req: HttpRequest,\n options: RequestOptions,\n): Promise<HttpResponse<T>> {\n const fetchImpl: FetchLike = options.fetch ?? (globalThis.fetch as FetchLike);\n const retry = req.retry ?? {};\n const maxAttempts = retry.maxAttempts ?? DEFAULT_MAX_ATTEMPTS;\n const initialDelayMs = retry.initialDelayMs ?? DEFAULT_INITIAL_DELAY_MS;\n const maxDelayMs = retry.maxDelayMs ?? DEFAULT_MAX_DELAY_MS;\n const retryOn = retry.retryOn ?? defaultRetryOn;\n const timeoutMs = req.timeoutMs ?? DEFAULT_TIMEOUT_MS;\n const parseJson = req.parseJson ?? true;\n\n const headers = mergeHeaders(\n {\n 'User-Agent': DEFAULT_USER_AGENT,\n Accept: 'application/json',\n },\n req.headers,\n );\n\n let lastErr: Error | undefined;\n\n for (let attempt = 0; attempt < maxAttempts; attempt++) {\n req.signal?.throwIfAborted();\n\n const { signal, cancel } = linkTimeoutSignal(req.signal, timeoutMs);\n let res: Response;\n try {\n res = await fetchImpl(req.url, {\n method: req.method ?? 'GET',\n headers,\n body: req.body as RequestInit['body'],\n signal,\n });\n } catch (err) {\n cancel();\n if (req.signal?.aborted) {\n throw req.signal.reason ?? err;\n }\n const error = err instanceof Error ? err : new Error(String(err));\n lastErr = error;\n if (attempt < maxAttempts - 1 && retryOn(null, error)) {\n const delay = computeDelay(attempt, initialDelayMs, maxDelayMs);\n await sleep(delay, req.signal);\n continue;\n }\n throw new TransientError(error.message);\n }\n cancel();\n\n const body = await readBody(res, parseJson);\n const httpResponse: HttpResponse<T> = {\n status: res.status,\n headers: res.headers,\n body: body as T,\n };\n if (req.rateLimit) {\n const state = req.rateLimit.parse(res.headers);\n if (state) {\n httpResponse.rateLimitState = state;\n }\n }\n\n if (options.observer) {\n await notifyObserver(options.observer, {\n url: req.url,\n method: req.method ?? 'GET',\n status: res.status,\n resource: options.resource,\n requestId: options.requestId ?? newRequestId(),\n body,\n });\n }\n\n if (res.ok) {\n return httpResponse;\n }\n\n const retryAfter = parseRetryAfter(res.headers.get('retry-after'));\n const message = `HTTP ${res.status} ${res.statusText} for ${req.method ?? 'GET'} ${req.url}`;\n const err = errorForStatus(message, httpResponse, retryAfter);\n\n if (\n attempt < maxAttempts - 1 &&\n retryOn(res.status, err) &&\n !(err instanceof AuthError) &&\n !(err instanceof ClientBugError)\n ) {\n lastErr = err;\n let delay = computeDelay(attempt, initialDelayMs, maxDelayMs);\n if (err instanceof RateLimitError && retryAfter) {\n const wait = retryAfter.getTime() - Date.now();\n if (wait > 0) {\n delay = Math.min(wait, maxDelayMs);\n }\n }\n await sleep(delay, req.signal);\n continue;\n }\n\n throw err;\n }\n\n throw lastErr ?? new UpstreamBugError('Exhausted retry attempts');\n}\n\nfunction computeDelay(\n attempt: number,\n initialDelayMs: number,\n maxDelayMs: number,\n): number {\n const base = initialDelayMs * 2 ** attempt;\n const jitter = base * 0.25 * Math.random();\n return Math.min(base + jitter, maxDelayMs);\n}\n\nexport { HttpClientError };\n","export interface RateLimitState {\n remaining: number;\n resetAt: Date;\n}\n\nexport interface RateLimitPolicy {\n parse(headers: Headers): RateLimitState | null;\n}\n\nexport interface StandardRateLimitPolicyConfig {\n remainingHeader: string;\n resetHeader: string;\n resetUnit: 's' | 'ms';\n resetFallbackMs?: number;\n}\n\nexport function standardRateLimitPolicy(\n config: StandardRateLimitPolicyConfig,\n): RateLimitPolicy {\n const { remainingHeader, resetHeader, resetUnit, resetFallbackMs } = config;\n const multiplier = resetUnit === 's' ? 1000 : 1;\n return {\n parse(h) {\n const remainingRaw = h.get(remainingHeader);\n if (remainingRaw === null || remainingRaw.trim() === '') {\n return null;\n }\n const remaining = Number(remainingRaw);\n if (!Number.isFinite(remaining)) {\n return null;\n }\n const resetRaw = h.get(resetHeader);\n if (resetRaw === null) {\n if (resetFallbackMs === undefined) {\n return null;\n }\n return {\n remaining,\n resetAt: new Date(Date.now() + resetFallbackMs),\n };\n }\n if (resetRaw.trim() === '') {\n return null;\n }\n const reset = Number(resetRaw);\n if (!Number.isFinite(reset) || reset < 0) {\n return null;\n }\n const resetMs = reset * multiplier;\n if (!Number.isFinite(resetMs)) {\n return null;\n }\n return { remaining, resetAt: new Date(resetMs) };\n },\n };\n}\n","export async function mapWithConcurrency<T, R>(\n items: readonly T[],\n concurrency: number,\n fn: (item: T, index: number) => Promise<R>,\n): Promise<R[]> {\n const results = new Array<R>(items.length);\n if (items.length === 0) {\n return results;\n }\n const normalized = Number.isFinite(concurrency) ? Math.floor(concurrency) : 1;\n const limit = Math.max(1, Math.min(normalized, items.length));\n let next = 0;\n let failed = false;\n\n async function worker(): Promise<void> {\n while (!failed) {\n const i = next++;\n if (i >= items.length) {\n return;\n }\n try {\n results[i] = await fn(items[i]!, i);\n } catch (err) {\n failed = true;\n throw err;\n }\n }\n }\n\n const workers: Promise<void>[] = [];\n for (let w = 0; w < limit; w++) {\n workers.push(worker());\n }\n await Promise.all(workers);\n return results;\n}\n","export interface SanitizeAllowedUrlOptions {\n url: string | null;\n host: string;\n pathname: string;\n protocol?: 'https:' | 'http:';\n}\n\nexport function sanitizeAllowedUrl(\n options: SanitizeAllowedUrlOptions,\n): string | null {\n const { url, host, pathname, protocol = 'https:' } = options;\n if (url === null) {\n return null;\n }\n try {\n const u = new URL(url);\n if (u.protocol !== protocol || u.host !== host || u.pathname !== pathname) {\n return null;\n }\n return u.toString();\n } catch {\n return null;\n }\n}\n","export type EpochUnit = 'ms' | 's' | 'iso';\n\nexport function parseEpoch(\n value: number | string | null | undefined,\n unit: EpochUnit,\n): number | null {\n if (value === null || value === undefined) {\n return null;\n }\n if (unit === 'iso') {\n if (typeof value !== 'string') {\n return null;\n }\n const ms = new Date(value).getTime();\n return Number.isFinite(ms) ? ms : null;\n }\n if (typeof value === 'string' && value.trim() === '') {\n return null;\n }\n const n = typeof value === 'number' ? value : Number(value);\n if (!Number.isFinite(n)) {\n return null;\n }\n const result = unit === 's' ? n * 1000 : n;\n return Number.isFinite(result) ? result : null;\n}\n","import { request } from './request';\nimport type { HttpRequest } from './types';\n\nexport function parseLinkHeader(header: string | null): Record<string, string> {\n if (!header) {\n return {};\n }\n const result: Record<string, string> = {};\n for (const part of header.split(',')) {\n const match = part.match(/<([^>]+)>\\s*;\\s*rel=\"([^\"]+)\"/);\n if (match) {\n result[match[2]!] = match[1]!;\n }\n }\n return result;\n}\n\nexport async function* paginateLink<T>(\n initial: HttpRequest,\n parse: (body: unknown) => T[],\n options: { resource: string },\n): AsyncIterable<T> {\n let next: string | null = initial.url;\n while (next) {\n const res: Awaited<ReturnType<typeof request>> = await request(\n {\n ...initial,\n url: next,\n },\n { resource: options.resource },\n );\n for (const item of parse(res.body)) {\n yield item;\n }\n const links = parseLinkHeader(res.headers.get('link'));\n next = links['next'] ?? null;\n }\n}\n\nexport async function* paginateCursor<T>(\n initial: HttpRequest,\n parse: (body: unknown) => { items: T[]; nextCursor: string | null },\n buildNext: (req: HttpRequest, cursor: string) => HttpRequest,\n options: { resource: string },\n): AsyncIterable<T> {\n let req: HttpRequest = initial;\n while (true) {\n const res = await request(req, { resource: options.resource });\n const { items, nextCursor } = parse(res.body);\n for (const item of items) {\n yield item;\n }\n if (!nextCursor) {\n return;\n }\n req = buildNext(req, nextCursor);\n }\n}\n\nexport async function* paginatePage<T>(\n initial: HttpRequest,\n parse: (body: unknown) => { items: T[]; hasMore: boolean },\n buildPage: (req: HttpRequest, page: number) => HttpRequest,\n options: { resource: string },\n): AsyncIterable<T> {\n let page = 1;\n while (true) {\n const req = page === 1 ? initial : buildPage(initial, page);\n const res = await request(req, { resource: options.resource });\n const { items, hasMore } = parse(res.body);\n for (const item of items) {\n yield item;\n }\n if (!hasMore || items.length === 0) {\n return;\n }\n page++;\n }\n}\n","export type LogFields = Record<string, unknown>;\n\nexport interface ConnectorLogger {\n info(event: string, fields?: LogFields): void;\n warn(event: string, fields?: LogFields): void;\n}\n\nexport interface ConnectorLoggerOptions {\n scope: string;\n}\n\nconst MAX_VALUE_LEN = 120;\n\nfunction truncate(s: string, max = MAX_VALUE_LEN): string {\n if (s.length <= max) {\n return s;\n }\n return `${s.slice(0, max - 1)}…`;\n}\n\nfunction formatValue(value: unknown): string {\n if (value === null) {\n return 'null';\n }\n if (value === undefined) {\n return '';\n }\n if (typeof value === 'number' || typeof value === 'boolean') {\n return String(value);\n }\n if (typeof value === 'string') {\n const t = truncate(value);\n if (/[\\s\"=]/.test(t)) {\n return JSON.stringify(t);\n }\n return t;\n }\n if (typeof value === 'bigint') {\n return value.toString();\n }\n let json: string | undefined;\n try {\n json = JSON.stringify(value);\n } catch {\n json = undefined;\n }\n return truncate(json ?? String(value));\n}\n\nexport function formatLogFields(fields?: LogFields): string {\n if (!fields) {\n return '';\n }\n const parts: string[] = [];\n for (const [k, v] of Object.entries(fields)) {\n if (v === undefined) {\n continue;\n }\n parts.push(`${k}=${formatValue(v)}`);\n }\n return parts.length > 0 ? ` ${parts.join(' ')}` : '';\n}\n\nexport function formatLogLine(\n scope: string,\n event: string,\n fields?: LogFields,\n): string {\n return `[${scope}] ${event}${formatLogFields(fields)}`;\n}\n\nexport function createDefaultConnectorLogger(\n opts: ConnectorLoggerOptions,\n): ConnectorLogger {\n return {\n info(event, fields) {\n console.info(formatLogLine(opts.scope, event, fields));\n },\n warn(event, fields) {\n console.warn(formatLogLine(opts.scope, event, fields));\n },\n };\n}\n\nconst NOOP_LOGGER: ConnectorLogger = {\n info() {},\n warn() {},\n};\n\nexport function noopConnectorLogger(): ConnectorLogger {\n return NOOP_LOGGER;\n}\n","import {\n type HttpResponse,\n connectorUserAgent,\n parseEpoch,\n} from '@rawdash/connector-shared';\nimport {\n BaseConnector,\n type ConnectorContext,\n type ConnectorDoc,\n type CredentialsSchema,\n type JSONValue,\n type MetricSample,\n type StorageHandle,\n type SyncOptions,\n type SyncResult,\n defineConfigFields,\n defineConnectorDoc,\n defineResources,\n makeChunkedCursorGuard,\n schemasFromResources,\n selectActivePhases,\n} from '@rawdash/core';\nimport { z } from 'zod';\n\nconst ANTHROPIC_API_HOST = 'api.anthropic.com';\nconst ANTHROPIC_API_BASE = `https://${ANTHROPIC_API_HOST}`;\nconst ANTHROPIC_API_VERSION = '2023-06-01';\nconst USAGE_PAGE_LIMIT = 31;\nconst COSTS_PAGE_LIMIT = 31;\nconst MS_PER_DAY = 86_400_000;\nconst DEFAULT_LOOKBACK_DAYS = 30;\nconst INCREMENTAL_LOOKBACK_DAYS = 2;\n// Cost report `amount` is a decimal string in the lowest currency unit (cents\n// for USD): e.g. \"123.45\" represents $1.2345. Divide by 100 to get dollars.\nconst COST_AMOUNT_DIVISOR = 100;\n\nexport const configFields = defineConfigFields(\n z.object({\n adminApiKey: z.object({ $secret: z.string().min(1) }).meta({\n label: 'Admin API key',\n description:\n 'Anthropic organization admin API key (starts with sk-ant-admin-). Create one at console.anthropic.com -> Settings -> Admin keys. Regular API keys (sk-ant-api-) cannot read the Usage and Cost reports.',\n placeholder: 'ANTHROPIC_ADMIN_API_KEY',\n secret: true,\n }),\n workspaceIds: z.array(z.string().min(1)).nonempty().optional().meta({\n label: 'Workspace IDs (optional)',\n description:\n 'Restrict usage and cost queries to specific Anthropic workspace ids (wrkspc_...). Omit to aggregate every workspace the admin key can see.',\n }),\n resources: z\n .array(\n z.enum([\n 'anthropic_input_tokens',\n 'anthropic_output_tokens',\n 'anthropic_cache_read_tokens',\n 'anthropic_cache_creation_tokens',\n 'anthropic_web_search_requests',\n 'anthropic_cost_usd',\n ]),\n )\n .nonempty()\n .optional()\n .meta({\n label: 'Resources',\n description:\n 'Which Anthropic metric series to sync. Omit to sync all of them. The five usage metrics share one upstream call to the Messages Usage Report; enabling any one of them fetches the report and writes all five.',\n }),\n lookbackDays: z.number().int().positive().max(180).optional().meta({\n label: 'Backfill window (days)',\n description:\n 'How many days of usage history to fetch on a full sync. Defaults to 30. The Usage Report returns at most 31 buckets per page, so longer windows paginate.',\n placeholder: '30',\n }),\n }),\n);\n\nexport const doc: ConnectorDoc = defineConnectorDoc({\n displayName: 'Anthropic',\n category: 'engineering',\n brandColor: '#D97757',\n tagline:\n 'Track Anthropic spend, daily token usage across Claude models, cache hit volumes, and web-search tool requests from the Anthropic Admin API.',\n vendor: {\n name: 'Anthropic',\n domain: 'anthropic.com',\n apiDocs:\n 'https://docs.claude.com/en/api/admin-api/usage-cost/get-messages-usage-report',\n website: 'https://anthropic.com',\n },\n auth: {\n summary:\n 'Authenticates with an Anthropic organization admin API key (sk-ant-admin-). Admin keys are the only key class that can read the Usage and Cost reports; regular API keys return 403.',\n setup: [\n 'Open console.anthropic.com -> Settings -> Admin Keys and create a new admin key. Admin keys are organization-scoped, so create the key from the organization whose usage you want to read.',\n 'Store the key as a secret (e.g. ANTHROPIC_ADMIN_API_KEY).',\n 'Reference it from config as `adminApiKey: secret(\"ANTHROPIC_ADMIN_API_KEY\")`.',\n 'Optionally set `workspaceIds` to restrict the query to a subset of workspaces.',\n ],\n },\n rateLimit:\n 'The Admin API returns 429 with a Retry-After header on burst; the shared HTTP client honors it automatically. Daily syncs against the Usage and Cost reports are well below the per-organization Admin API budget.',\n limitations: [\n 'Only the organization Messages Usage Report and Cost Report endpoints are synced. Per-request logs and individual message bodies are not exposed by the Admin API.',\n 'All samples are bucketed daily (1d bucket_width). The Usage Report also supports hourly and per-minute granularity but those are not exposed here in v1.',\n 'The Cost Report only supports 1d bucket_width and reports cost in USD; non-USD billing currencies are not converted.',\n 'Admin API keys are required - regular sk-ant-api- keys do not have access to the organization Usage and Cost reports.',\n ],\n});\n\nconst PHASE_ORDER = ['usage_messages', 'cost_report'] as const;\n\ntype AnthropicPhase = (typeof PHASE_ORDER)[number];\n\nexport type AnthropicResource =\n | 'anthropic_input_tokens'\n | 'anthropic_output_tokens'\n | 'anthropic_cache_read_tokens'\n | 'anthropic_cache_creation_tokens'\n | 'anthropic_web_search_requests'\n | 'anthropic_cost_usd';\n\nconst isAnthropicSyncCursor = makeChunkedCursorGuard(PHASE_ORDER);\n\nconst RESOURCES_BY_PHASE: Record<AnthropicPhase, readonly AnthropicResource[]> =\n {\n usage_messages: [\n 'anthropic_input_tokens',\n 'anthropic_output_tokens',\n 'anthropic_cache_read_tokens',\n 'anthropic_cache_creation_tokens',\n 'anthropic_web_search_requests',\n ],\n cost_report: ['anthropic_cost_usd'],\n };\n\nconst PHASE_ENDPOINT_PATH: Record<AnthropicPhase, string> = {\n usage_messages: '/v1/organizations/usage_report/messages',\n cost_report: '/v1/organizations/cost_report',\n};\n\nconst usageCacheCreationSchema = z.object({\n ephemeral_1h_input_tokens: z.number().nonnegative().nullish(),\n ephemeral_5m_input_tokens: z.number().nonnegative().nullish(),\n});\n\nconst usageServerToolUseSchema = z.object({\n web_search_requests: z.number().int().nonnegative().nullish(),\n});\n\nconst usageResultSchema = z.object({\n account_id: z.string().nullish(),\n api_key_id: z.string().nullish(),\n cache_creation: usageCacheCreationSchema.nullish(),\n cache_read_input_tokens: z.number().nonnegative(),\n context_window: z.string().nullish(),\n inference_geo: z.string().nullish(),\n model: z.string().nullish(),\n output_tokens: z.number().nonnegative(),\n server_tool_use: usageServerToolUseSchema.nullish(),\n service_account_id: z.string().nullish(),\n service_tier: z.string().nullish(),\n uncached_input_tokens: z.number().nonnegative(),\n workspace_id: z.string().nullish(),\n});\n\nconst costResultSchema = z.object({\n amount: z.string(),\n context_window: z.string().nullish(),\n cost_type: z.string().nullish(),\n currency: z.string(),\n description: z.string().nullish(),\n inference_geo: z.string().nullish(),\n model: z.string().nullish(),\n service_tier: z.string().nullish(),\n token_type: z.string().nullish(),\n workspace_id: z.string().nullish(),\n});\n\nfunction bucketResponseSchema<T extends z.ZodTypeAny>(resultSchema: T) {\n return z.object({\n data: z.array(\n z.object({\n starting_at: z.string(),\n ending_at: z.string(),\n results: z.array(resultSchema),\n }),\n ),\n has_more: z.boolean(),\n next_page: z.string().nullish(),\n });\n}\n\nconst usageResponseSchema = bucketResponseSchema(usageResultSchema);\nconst costResponseSchema = bucketResponseSchema(costResultSchema);\n\ntype UsageResult = z.infer<typeof usageResultSchema>;\ntype CostResult = z.infer<typeof costResultSchema>;\n\ninterface BucketPage<TResult> {\n starting_at: string;\n ending_at: string;\n results: TResult[];\n}\n\ninterface PageResponse<TResult> {\n buckets: BucketPage<TResult>[];\n nextPage: string | null;\n}\n\nconst USAGE_DIMENSIONS = [\n {\n name: 'model',\n description: 'Claude model id reported by Anthropic (or null).',\n },\n {\n name: 'workspace_id',\n description:\n 'Anthropic workspace id the usage is attributed to (or null for the default workspace).',\n },\n {\n name: 'api_key_id',\n description: 'API key id the usage is attributed to (or null).',\n },\n {\n name: 'service_tier',\n description:\n 'Service tier the request ran under (standard, batch, priority, flex, etc.), or null.',\n },\n {\n name: 'context_window',\n description:\n 'Context window bucket the request used (0-200k or 200k-1M), or null.',\n },\n {\n name: 'inference_geo',\n description:\n 'Inference geo the request ran in (global, us, not_available), or null.',\n },\n] as const;\n\nconst COST_DIMENSIONS = [\n {\n name: 'workspace_id',\n description:\n 'Anthropic workspace id the cost is attributed to (or null for the default workspace).',\n },\n {\n name: 'description',\n description:\n 'Human-readable cost line item label (e.g. \"Claude Sonnet 4 Usage - Input Tokens\"), or null when ungrouped.',\n },\n {\n name: 'cost_type',\n description:\n 'Cost category (tokens, web_search, code_execution, session_usage), or null.',\n },\n {\n name: 'model',\n description:\n 'Claude model the cost is attributed to (or null for non-token costs).',\n },\n {\n name: 'token_type',\n description:\n 'Token category for token costs (uncached_input_tokens, output_tokens, cache_read_input_tokens, cache_creation.ephemeral_*_input_tokens), or null.',\n },\n {\n name: 'service_tier',\n description:\n 'Service tier the cost is attributed to (standard or batch), or null.',\n },\n {\n name: 'context_window',\n description:\n 'Context window the cost is attributed to (0-200k or 200k-1M), or null.',\n },\n {\n name: 'currency',\n description:\n 'Billing currency reported by Anthropic (currently always USD).',\n },\n] as const;\n\nexport const anthropicResources = defineResources({\n anthropic_input_tokens: {\n shape: 'metric',\n description:\n 'Daily uncached input tokens processed by the Anthropic Messages API, grouped by model and workspace.',\n endpoint: 'GET /v1/organizations/usage_report/messages',\n unit: 'tokens',\n granularity: 'daily',\n dimensions: [...USAGE_DIMENSIONS],\n notes:\n 'Sample value is uncached_input_tokens. Cache-read and cache-creation token volumes are mirrored on their own metrics so a cache hit ratio can be computed at query time.',\n responses: { usage_messages: usageResponseSchema },\n },\n anthropic_output_tokens: {\n shape: 'metric',\n description:\n 'Daily output tokens generated by the Anthropic Messages API, grouped by model and workspace.',\n endpoint: 'GET /v1/organizations/usage_report/messages',\n unit: 'tokens',\n granularity: 'daily',\n dimensions: [...USAGE_DIMENSIONS],\n notes:\n 'Written alongside anthropic_input_tokens from the same usage_messages API call.',\n },\n anthropic_cache_read_tokens: {\n shape: 'metric',\n description:\n 'Daily input tokens read from the prompt cache, grouped by model and workspace.',\n endpoint: 'GET /v1/organizations/usage_report/messages',\n unit: 'tokens',\n granularity: 'daily',\n dimensions: [...USAGE_DIMENSIONS],\n notes:\n 'Cache hits are charged at a fraction of the uncached rate, so this metric paired with anthropic_input_tokens gives the cache hit ratio.',\n },\n anthropic_cache_creation_tokens: {\n shape: 'metric',\n description:\n 'Daily input tokens written into the prompt cache (sum of the 1h and 5m ephemeral caches), grouped by model and workspace.',\n endpoint: 'GET /v1/organizations/usage_report/messages',\n unit: 'tokens',\n granularity: 'daily',\n dimensions: [...USAGE_DIMENSIONS],\n notes:\n 'The per-cache-bucket counts (ephemeral_1h_input_tokens, ephemeral_5m_input_tokens) are mirrored in attributes for finer-grained widgets.',\n },\n anthropic_web_search_requests: {\n shape: 'metric',\n description:\n 'Daily count of web-search tool requests executed server-side by Claude, grouped by model and workspace.',\n endpoint: 'GET /v1/organizations/usage_report/messages',\n unit: 'requests',\n granularity: 'daily',\n dimensions: [...USAGE_DIMENSIONS],\n notes:\n 'Sourced from server_tool_use.web_search_requests on each usage bucket. Zero rows are still written so a \"no usage today\" widget renders correctly.',\n },\n anthropic_cost_usd: {\n shape: 'metric',\n description:\n 'Daily organization spend in USD, broken down by workspace and cost line item, pulled from the Anthropic Cost Report.',\n endpoint: 'GET /v1/organizations/cost_report',\n unit: 'USD',\n granularity: 'daily',\n dimensions: [...COST_DIMENSIONS],\n notes:\n 'The Cost Report returns amounts as a decimal string in the lowest currency unit (cents for USD). The connector divides by 100 so the stored metric value is dollars. Costs can be revised for a couple of days after the fact; incremental syncs refetch a short trailing window to pick up adjustments.',\n responses: { cost_report: costResponseSchema },\n },\n});\n\nexport interface AnthropicSettings {\n workspaceIds?: readonly string[];\n resources?: readonly AnthropicResource[];\n lookbackDays?: number;\n}\n\nconst anthropicCredentials = {\n adminApiKey: {\n description: 'Anthropic organization admin API key (sk-ant-admin-...)',\n auth: 'required' as const,\n },\n} satisfies CredentialsSchema;\n\ntype AnthropicCredentials = typeof anthropicCredentials;\n\nexport const id = 'anthropic';\n\ninterface UsageWindow {\n startingAt: string;\n endingAt: string;\n}\n\nexport function getUsageWindow(\n options: SyncOptions,\n lookbackDays: number,\n now: number = Date.now(),\n): UsageWindow {\n const todayStart = Math.floor(now / MS_PER_DAY) * MS_PER_DAY;\n const endMs = todayStart + MS_PER_DAY;\n\n let days = lookbackDays;\n if (options.mode === 'latest') {\n days = INCREMENTAL_LOOKBACK_DAYS;\n } else if (options.since !== undefined) {\n const sinceMs = parseEpoch(options.since, 'iso');\n if (sinceMs !== null) {\n const elapsed = Math.ceil((now - sinceMs) / MS_PER_DAY);\n days = Math.min(\n Math.max(elapsed + INCREMENTAL_LOOKBACK_DAYS, 1),\n lookbackDays,\n );\n }\n }\n const startMs = endMs - days * MS_PER_DAY;\n return {\n startingAt: new Date(startMs).toISOString(),\n endingAt: new Date(endMs).toISOString(),\n };\n}\n\nfunction resourceToPhase(resource: AnthropicResource): AnthropicPhase {\n for (const phase of PHASE_ORDER) {\n if ((RESOURCES_BY_PHASE[phase] as readonly string[]).includes(resource)) {\n return phase;\n }\n }\n // unreachable - RESOURCES_BY_PHASE covers every AnthropicResource\n throw new Error(`anthropic: unmapped resource ${resource}`);\n}\n\nfunction nullableString(value: string | null | undefined): string | null {\n return value === undefined || value === null ? null : value;\n}\n\nfunction usageDimensionAttributes(row: UsageResult): Record<string, JSONValue> {\n return {\n model: nullableString(row.model),\n workspace_id: nullableString(row.workspace_id),\n api_key_id: nullableString(row.api_key_id),\n service_tier: nullableString(row.service_tier),\n context_window: nullableString(row.context_window),\n inference_geo: nullableString(row.inference_geo),\n account_id: nullableString(row.account_id),\n service_account_id: nullableString(row.service_account_id),\n };\n}\n\nfunction cacheCreationTotal(row: UsageResult): number {\n const c = row.cache_creation;\n if (!c) {\n return 0;\n }\n return (\n (c.ephemeral_1h_input_tokens ?? 0) + (c.ephemeral_5m_input_tokens ?? 0)\n );\n}\n\nfunction tsFromBucket(bucket: BucketPage<unknown>): number | null {\n return parseEpoch(bucket.starting_at, 'iso');\n}\n\nexport function buildUsageSamples(\n buckets: readonly BucketPage<UsageResult>[],\n): {\n inputTokens: MetricSample[];\n outputTokens: MetricSample[];\n cacheReadTokens: MetricSample[];\n cacheCreationTokens: MetricSample[];\n webSearchRequests: MetricSample[];\n} {\n const inputTokens: MetricSample[] = [];\n const outputTokens: MetricSample[] = [];\n const cacheReadTokens: MetricSample[] = [];\n const cacheCreationTokens: MetricSample[] = [];\n const webSearchRequests: MetricSample[] = [];\n for (const bucket of buckets) {\n const ts = tsFromBucket(bucket);\n if (ts === null) {\n continue;\n }\n for (const row of bucket.results) {\n const common = usageDimensionAttributes(row);\n const cacheCreation = row.cache_creation;\n inputTokens.push({\n name: 'anthropic_input_tokens',\n ts,\n value: row.uncached_input_tokens,\n attributes: { ...common },\n });\n outputTokens.push({\n name: 'anthropic_output_tokens',\n ts,\n value: row.output_tokens,\n attributes: { ...common },\n });\n cacheReadTokens.push({\n name: 'anthropic_cache_read_tokens',\n ts,\n value: row.cache_read_input_tokens,\n attributes: { ...common },\n });\n cacheCreationTokens.push({\n name: 'anthropic_cache_creation_tokens',\n ts,\n value: cacheCreationTotal(row),\n attributes: {\n ...common,\n ephemeral_1h_input_tokens:\n cacheCreation?.ephemeral_1h_input_tokens ?? 0,\n ephemeral_5m_input_tokens:\n cacheCreation?.ephemeral_5m_input_tokens ?? 0,\n },\n });\n webSearchRequests.push({\n name: 'anthropic_web_search_requests',\n ts,\n value: row.server_tool_use?.web_search_requests ?? 0,\n attributes: { ...common },\n });\n }\n }\n return {\n inputTokens,\n outputTokens,\n cacheReadTokens,\n cacheCreationTokens,\n webSearchRequests,\n };\n}\n\nexport function buildCostSamples(\n buckets: readonly BucketPage<CostResult>[],\n): MetricSample[] {\n const samples: MetricSample[] = [];\n for (const bucket of buckets) {\n const ts = tsFromBucket(bucket);\n if (ts === null) {\n continue;\n }\n for (const row of bucket.results) {\n const rawAmount = Number.parseFloat(row.amount);\n const value = Number.isFinite(rawAmount)\n ? rawAmount / COST_AMOUNT_DIVISOR\n : 0;\n samples.push({\n name: 'anthropic_cost_usd',\n ts,\n value,\n attributes: {\n workspace_id: nullableString(row.workspace_id),\n description: nullableString(row.description),\n cost_type: nullableString(row.cost_type),\n model: nullableString(row.model),\n token_type: nullableString(row.token_type),\n service_tier: nullableString(row.service_tier),\n context_window: nullableString(row.context_window),\n currency: row.currency,\n },\n });\n }\n }\n return samples;\n}\n\nexport class AnthropicConnector extends BaseConnector<\n AnthropicSettings,\n AnthropicCredentials\n> {\n static readonly id = id;\n\n static readonly resources = anthropicResources;\n\n static readonly schemas = schemasFromResources(anthropicResources);\n\n static create(input: unknown, ctx?: ConnectorContext): AnthropicConnector {\n const parsed = configFields.parse(input);\n return new AnthropicConnector(\n {\n workspaceIds: parsed.workspaceIds,\n resources: parsed.resources,\n lookbackDays: parsed.lookbackDays,\n },\n { adminApiKey: parsed.adminApiKey },\n ctx,\n );\n }\n\n readonly id = id;\n override readonly credentials = anthropicCredentials;\n\n private buildHeaders(): Record<string, string> {\n return {\n 'X-Api-Key': String(this.creds.adminApiKey),\n 'anthropic-version': ANTHROPIC_API_VERSION,\n 'User-Agent': connectorUserAgent(this.id),\n };\n }\n\n private fetch<T>(\n url: string,\n resource: string,\n signal?: AbortSignal,\n ): Promise<HttpResponse<T>> {\n return this.get<T>(url, {\n resource,\n headers: this.buildHeaders(),\n signal,\n });\n }\n\n private buildInitialUrl(phase: AnthropicPhase, window: UsageWindow): string {\n const url = new URL(`${ANTHROPIC_API_BASE}${PHASE_ENDPOINT_PATH[phase]}`);\n url.searchParams.set('starting_at', window.startingAt);\n url.searchParams.set('ending_at', window.endingAt);\n url.searchParams.set('bucket_width', '1d');\n if (phase === 'usage_messages') {\n url.searchParams.set('limit', String(USAGE_PAGE_LIMIT));\n url.searchParams.append('group_by', 'model');\n url.searchParams.append('group_by', 'workspace_id');\n url.searchParams.append('group_by', 'api_key_id');\n url.searchParams.append('group_by', 'service_tier');\n url.searchParams.append('group_by', 'context_window');\n url.searchParams.append('group_by', 'inference_geo');\n for (const workspaceId of this.settings.workspaceIds ?? []) {\n url.searchParams.append('workspace_ids', workspaceId);\n }\n } else {\n url.searchParams.set('limit', String(COSTS_PAGE_LIMIT));\n url.searchParams.append('group_by', 'workspace_id');\n url.searchParams.append('group_by', 'description');\n }\n return url.toString();\n }\n\n private buildNextUrl(currentUrl: string, nextPage: string): string {\n const url = new URL(currentUrl);\n url.searchParams.set('page', nextPage);\n return url.toString();\n }\n\n private async fetchPhasePage<T>(\n phase: AnthropicPhase,\n schema: z.ZodType<T>,\n initialUrl: string,\n page: string | null,\n signal: AbortSignal | undefined,\n ): Promise<{ url: string; parsed: T; nextUrl: string | null }> {\n const url = page ?? initialUrl;\n const res = await this.fetch<unknown>(url, phase, signal);\n const parsed = schema.parse(res.body);\n const body = parsed as unknown as {\n next_page?: string | null;\n has_more?: boolean;\n };\n const nextPage =\n body.has_more === true &&\n typeof body.next_page === 'string' &&\n body.next_page.length > 0\n ? body.next_page\n : null;\n const nextUrl = nextPage ? this.buildNextUrl(url, nextPage) : null;\n return { url, parsed, nextUrl };\n }\n\n async sync(\n options: SyncOptions,\n storage: StorageHandle,\n signal?: AbortSignal,\n ): Promise<SyncResult> {\n const cursor = isAnthropicSyncCursor(options.cursor)\n ? options.cursor\n : undefined;\n const lookbackDays = this.settings.lookbackDays ?? DEFAULT_LOOKBACK_DAYS;\n const window = getUsageWindow(options, lookbackDays);\n\n const phases = selectActivePhases<AnthropicResource, AnthropicPhase>(\n resourceToPhase,\n PHASE_ORDER,\n this.settings.resources,\n );\n\n const startIdx = cursor ? phases.indexOf(cursor.phase) : 0;\n const resumeIdx = startIdx >= 0 ? startIdx : 0;\n\n for (let i = resumeIdx; i < phases.length; i++) {\n const phase = phases[i]!;\n if (signal?.aborted) {\n return { done: false, cursor: { phase, page: null } };\n }\n const phaseStart = Date.now();\n const initialUrl = this.buildInitialUrl(phase, window);\n let pageUrl: string | null = null;\n let pageCount = 0;\n const buckets: BucketPage<unknown>[] = [];\n\n while (true) {\n if (signal?.aborted) {\n return { done: false, cursor: { phase, page: null } };\n }\n pageCount += 1;\n const { parsed, nextUrl } = await this.fetchAnyPhasePage(\n phase,\n initialUrl,\n pageUrl,\n signal,\n );\n const data = parsed.data as BucketPage<unknown>[];\n buckets.push(...data);\n this.logger.info('fetched page', {\n resource: phase,\n page: pageCount,\n items: data.length,\n });\n if (nextUrl === null) {\n break;\n }\n pageUrl = nextUrl;\n }\n\n await this.writePhase(storage, phase, buckets);\n this.logger.info('resource done', {\n resource: phase,\n pages: pageCount,\n items: buckets.length,\n duration_ms: Date.now() - phaseStart,\n });\n }\n\n return { done: true };\n }\n\n private async fetchAnyPhasePage(\n phase: AnthropicPhase,\n initialUrl: string,\n page: string | null,\n signal: AbortSignal | undefined,\n ): Promise<{\n parsed: { data: BucketPage<unknown>[] };\n nextUrl: string | null;\n }> {\n switch (phase) {\n case 'usage_messages':\n return this.fetchPhasePage(\n phase,\n usageResponseSchema,\n initialUrl,\n page,\n signal,\n );\n case 'cost_report':\n return this.fetchPhasePage(\n phase,\n costResponseSchema,\n initialUrl,\n page,\n signal,\n );\n }\n }\n\n private async writePhase(\n storage: StorageHandle,\n phase: AnthropicPhase,\n buckets: BucketPage<unknown>[],\n ): Promise<void> {\n switch (phase) {\n case 'usage_messages': {\n const samples = buildUsageSamples(buckets as BucketPage<UsageResult>[]);\n await storage.metrics(samples.inputTokens, {\n names: ['anthropic_input_tokens'],\n });\n await storage.metrics(samples.outputTokens, {\n names: ['anthropic_output_tokens'],\n });\n await storage.metrics(samples.cacheReadTokens, {\n names: ['anthropic_cache_read_tokens'],\n });\n await storage.metrics(samples.cacheCreationTokens, {\n names: ['anthropic_cache_creation_tokens'],\n });\n await storage.metrics(samples.webSearchRequests, {\n names: ['anthropic_web_search_requests'],\n });\n return;\n }\n case 'cost_report': {\n const samples = buildCostSamples(buckets as BucketPage<CostResult>[]);\n await storage.metrics(samples, { names: ['anthropic_cost_usd'] });\n return;\n }\n }\n }\n}\n\nexport type { PageResponse, BucketPage, UsageResult, CostResult };\n","import { AnthropicConnector } from './anthropic';\n\nexport {\n AnthropicConnector,\n anthropicResources as resources,\n buildCostSamples,\n buildUsageSamples,\n configFields,\n doc,\n getUsageWindow,\n id,\n} from './anthropic';\nexport type {\n AnthropicResource,\n AnthropicSettings,\n BucketPage,\n CostResult,\n PageResponse,\n UsageResult,\n} from './anthropic';\nexport default AnthropicConnector;\n"],"mappings":"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","names":[]}
|
|
1
|
+
{"version":3,"sources":["../../../connector-shared/src/errors.ts","../../../connector-shared/src/retry.ts","../../../connector-shared/src/version.ts","../../../connector-shared/src/request.ts","../../../connector-shared/src/rate-limit.ts","../../../connector-shared/src/map-concurrent.ts","../../../connector-shared/src/sanitize.ts","../../../connector-shared/src/epoch.ts","../../../connector-shared/src/pagination.ts","../../../connector-shared/src/logger.ts","../src/anthropic.ts","../src/index.ts"],"sourcesContent":["import type { HttpResponse } from './types';\n\nexport type HttpErrorKind =\n | 'transient'\n | 'rate_limit'\n | 'auth'\n | 'upstream_bug'\n | 'client_bug';\n\nexport abstract class HttpClientError extends Error {\n abstract readonly kind: HttpErrorKind;\n readonly response?: HttpResponse;\n\n constructor(message: string, response?: HttpResponse) {\n super(message);\n this.name = new.target.name;\n this.response = response;\n }\n}\n\nexport class TransientError extends HttpClientError {\n readonly kind = 'transient' as const;\n}\n\nexport class RateLimitError extends HttpClientError {\n readonly kind = 'rate_limit' as const;\n readonly retryAfter?: Date;\n\n constructor(message: string, response?: HttpResponse, retryAfter?: Date) {\n super(message, response);\n this.retryAfter = retryAfter;\n }\n}\n\nexport class AuthError extends HttpClientError {\n readonly kind = 'auth' as const;\n}\n\nexport class UpstreamBugError extends HttpClientError {\n readonly kind = 'upstream_bug' as const;\n}\n\nexport class ClientBugError extends HttpClientError {\n readonly kind = 'client_bug' as const;\n}\n\nexport function classifyStatus(status: number): HttpErrorKind {\n if (status === 429) {\n return 'rate_limit';\n }\n if (status === 401 || status === 403) {\n return 'auth';\n }\n if (status === 408) {\n return 'transient';\n }\n if (status >= 500) {\n return 'upstream_bug';\n }\n if (status >= 400) {\n return 'client_bug';\n }\n return 'client_bug';\n}\n\nexport function errorForStatus(\n message: string,\n response: HttpResponse,\n retryAfter?: Date,\n): HttpClientError {\n const kind = classifyStatus(response.status);\n switch (kind) {\n case 'rate_limit':\n return new RateLimitError(message, response, retryAfter);\n case 'auth':\n return new AuthError(message, response);\n case 'transient':\n return new TransientError(message, response);\n case 'upstream_bug':\n return new UpstreamBugError(message, response);\n case 'client_bug':\n return new ClientBugError(message, response);\n }\n}\n","import { HttpClientError, RateLimitError, TransientError } from './errors';\n\nexport interface RetryPolicy {\n maxAttempts?: number;\n initialDelayMs?: number;\n maxDelayMs?: number;\n retryOn?: (status: number | null, err?: Error) => boolean;\n}\n\nexport const defaultRetryOn = (status: number | null, err?: Error): boolean => {\n if (err instanceof RateLimitError) {\n return true;\n }\n if (err instanceof TransientError) {\n return true;\n }\n if (status === null) {\n return err instanceof Error && !(err instanceof HttpClientError);\n }\n if (status === 408 || status === 429) {\n return true;\n }\n if (status >= 500) {\n return true;\n }\n return false;\n};\n\nexport function backoffDelayMs(\n attempt: number,\n policy: Required<Pick<RetryPolicy, 'initialDelayMs' | 'maxDelayMs'>>,\n): number {\n const base = policy.initialDelayMs * 2 ** attempt;\n const jitter = base * 0.25 * Math.random();\n return Math.min(base + jitter, policy.maxDelayMs);\n}\n\nexport function parseRetryAfter(\n headerValue: string | null,\n now: Date = new Date(),\n): Date | undefined {\n if (!headerValue) {\n return undefined;\n }\n const trimmed = headerValue.trim();\n if (/^\\d+$/.test(trimmed)) {\n return new Date(now.getTime() + Number(trimmed) * 1000);\n }\n const parsed = Date.parse(trimmed);\n if (Number.isNaN(parsed)) {\n return undefined;\n }\n return new Date(parsed);\n}\n\nexport function sleep(ms: number, signal?: AbortSignal): Promise<void> {\n if (signal?.aborted) {\n return Promise.reject(signal.reason ?? new Error('Aborted'));\n }\n return new Promise<void>((resolve, reject) => {\n const onAbort = () => {\n clearTimeout(timer);\n reject(signal!.reason ?? new Error('Aborted'));\n };\n const timer = setTimeout(() => {\n signal?.removeEventListener('abort', onAbort);\n resolve();\n }, ms);\n signal?.addEventListener('abort', onAbort, { once: true });\n });\n}\n","export const HTTP_CLIENT_VERSION = '0.0.0';\n\nexport const DEFAULT_USER_AGENT = `rawdash-connector/${HTTP_CLIENT_VERSION} (+https://rawdash.dev)`;\n\nexport function connectorUserAgent(connectorId: string): string {\n return `rawdash-connector-${connectorId}/${HTTP_CLIENT_VERSION} (+https://rawdash.dev)`;\n}\n","import {\n AuthError,\n ClientBugError,\n HttpClientError,\n RateLimitError,\n TransientError,\n UpstreamBugError,\n errorForStatus,\n} from './errors';\nimport { defaultRetryOn, parseRetryAfter, sleep } from './retry';\nimport type { FetchLike, HttpMethod, HttpRequest, HttpResponse } from './types';\nimport { DEFAULT_USER_AGENT } from './version';\n\nconst DEFAULT_TIMEOUT_MS = 10_000;\nconst DEFAULT_MAX_ATTEMPTS = 3;\nconst DEFAULT_INITIAL_DELAY_MS = 1000;\nconst DEFAULT_MAX_DELAY_MS = 60_000;\nconst OBSERVER_TIMEOUT_MS = 250;\n\nexport interface RequestObservation {\n url: string;\n method: HttpMethod;\n status: number;\n resource: string;\n requestId: string;\n body: unknown;\n}\n\nexport type RequestObserver = (\n event: RequestObservation,\n) => void | Promise<void>;\n\nexport interface RequestOptions {\n fetch?: FetchLike;\n observer?: RequestObserver;\n resource: string;\n requestId?: string;\n}\n\nasync function notifyObserver(\n observer: RequestObserver,\n event: RequestObservation,\n): Promise<void> {\n let result: void | Promise<void>;\n try {\n result = observer(event);\n } catch (err) {\n console.warn('[connector-shared] request observer threw:', err);\n return;\n }\n if (!(result instanceof Promise)) {\n return;\n }\n const guarded = result.catch((err) => {\n console.warn('[connector-shared] request observer rejected:', err);\n });\n let timer: ReturnType<typeof setTimeout> | undefined;\n const timeout = new Promise<void>((resolve) => {\n timer = setTimeout(resolve, OBSERVER_TIMEOUT_MS);\n });\n try {\n await Promise.race([guarded, timeout]);\n } finally {\n if (timer) {\n clearTimeout(timer);\n }\n }\n}\n\nfunction newRequestId(): string {\n const c = (globalThis as { crypto?: { randomUUID?: () => string } }).crypto;\n if (c?.randomUUID) {\n return c.randomUUID();\n }\n return `${Date.now().toString(36)}-${Math.random().toString(36).slice(2, 10)}`;\n}\n\nfunction mergeHeaders(\n defaults: Record<string, string>,\n overrides: Record<string, string> | undefined,\n): Record<string, string> {\n const merged: Record<string, string> = {};\n for (const [k, v] of Object.entries(defaults)) {\n merged[k.toLowerCase()] = v;\n }\n if (overrides) {\n for (const [k, v] of Object.entries(overrides)) {\n merged[k.toLowerCase()] = v;\n }\n }\n return merged;\n}\n\nfunction linkTimeoutSignal(\n parent: AbortSignal | undefined,\n timeoutMs: number,\n): { signal: AbortSignal; cancel: () => void } {\n const controller = new AbortController();\n const onParentAbort = () => {\n controller.abort(parent?.reason);\n };\n if (parent) {\n if (parent.aborted) {\n controller.abort(parent.reason);\n } else {\n parent.addEventListener('abort', onParentAbort, { once: true });\n }\n }\n const timer = setTimeout(() => {\n controller.abort(new Error(`Request timed out after ${timeoutMs}ms`));\n }, timeoutMs);\n return {\n signal: controller.signal,\n cancel: () => {\n clearTimeout(timer);\n if (parent) {\n parent.removeEventListener('abort', onParentAbort);\n }\n },\n };\n}\n\nasync function readBody(\n res: Response,\n parseJson: boolean,\n binary: boolean,\n): Promise<unknown> {\n if (res.status === 204 || res.status === 205) {\n return null;\n }\n if (binary) {\n return new Uint8Array(await res.arrayBuffer());\n }\n const contentType = res.headers.get('content-type') ?? '';\n if (parseJson && contentType.includes('application/json')) {\n const text = await res.text();\n if (text.length === 0) {\n return null;\n }\n return JSON.parse(text);\n }\n return res.text();\n}\n\nexport async function request<T = unknown>(\n req: HttpRequest,\n options: RequestOptions,\n): Promise<HttpResponse<T>> {\n const fetchImpl: FetchLike = options.fetch ?? (globalThis.fetch as FetchLike);\n const retry = req.retry ?? {};\n const maxAttempts = retry.maxAttempts ?? DEFAULT_MAX_ATTEMPTS;\n const initialDelayMs = retry.initialDelayMs ?? DEFAULT_INITIAL_DELAY_MS;\n const maxDelayMs = retry.maxDelayMs ?? DEFAULT_MAX_DELAY_MS;\n const retryOn = retry.retryOn ?? defaultRetryOn;\n const timeoutMs = req.timeoutMs ?? DEFAULT_TIMEOUT_MS;\n const parseJson = req.parseJson ?? true;\n const binary = req.binary ?? false;\n\n const headers = mergeHeaders(\n {\n 'User-Agent': DEFAULT_USER_AGENT,\n Accept: 'application/json',\n },\n req.headers,\n );\n\n let lastErr: Error | undefined;\n\n for (let attempt = 0; attempt < maxAttempts; attempt++) {\n req.signal?.throwIfAborted();\n\n const { signal, cancel } = linkTimeoutSignal(req.signal, timeoutMs);\n let res: Response;\n try {\n res = await fetchImpl(req.url, {\n method: req.method ?? 'GET',\n headers,\n body: req.body as RequestInit['body'],\n signal,\n });\n } catch (err) {\n cancel();\n if (req.signal?.aborted) {\n throw req.signal.reason ?? err;\n }\n const error = err instanceof Error ? err : new Error(String(err));\n lastErr = error;\n if (attempt < maxAttempts - 1 && retryOn(null, error)) {\n const delay = computeDelay(attempt, initialDelayMs, maxDelayMs);\n await sleep(delay, req.signal);\n continue;\n }\n throw new TransientError(error.message);\n }\n cancel();\n\n const body = await readBody(res, parseJson, binary);\n const httpResponse: HttpResponse<T> = {\n status: res.status,\n headers: res.headers,\n body: body as T,\n };\n if (req.rateLimit) {\n const state = req.rateLimit.parse(res.headers);\n if (state) {\n httpResponse.rateLimitState = state;\n }\n }\n\n if (options.observer) {\n await notifyObserver(options.observer, {\n url: req.url,\n method: req.method ?? 'GET',\n status: res.status,\n resource: options.resource,\n requestId: options.requestId ?? newRequestId(),\n body,\n });\n }\n\n if (res.ok) {\n return httpResponse;\n }\n\n const retryAfter = parseRetryAfter(res.headers.get('retry-after'));\n const message = `HTTP ${res.status} ${res.statusText} for ${req.method ?? 'GET'} ${req.url}`;\n const err = errorForStatus(message, httpResponse, retryAfter);\n\n if (\n attempt < maxAttempts - 1 &&\n retryOn(res.status, err) &&\n !(err instanceof AuthError) &&\n !(err instanceof ClientBugError)\n ) {\n lastErr = err;\n let delay = computeDelay(attempt, initialDelayMs, maxDelayMs);\n if (err instanceof RateLimitError && retryAfter) {\n const wait = retryAfter.getTime() - Date.now();\n if (wait > 0) {\n delay = Math.min(wait, maxDelayMs);\n }\n }\n await sleep(delay, req.signal);\n continue;\n }\n\n throw err;\n }\n\n throw lastErr ?? new UpstreamBugError('Exhausted retry attempts');\n}\n\nfunction computeDelay(\n attempt: number,\n initialDelayMs: number,\n maxDelayMs: number,\n): number {\n const base = initialDelayMs * 2 ** attempt;\n const jitter = base * 0.25 * Math.random();\n return Math.min(base + jitter, maxDelayMs);\n}\n\nexport { HttpClientError };\n","export interface RateLimitState {\n remaining: number;\n resetAt: Date;\n}\n\nexport interface RateLimitPolicy {\n parse(headers: Headers): RateLimitState | null;\n}\n\nexport interface StandardRateLimitPolicyConfig {\n remainingHeader: string;\n resetHeader: string;\n resetUnit: 's' | 'ms';\n resetFallbackMs?: number;\n}\n\nexport function standardRateLimitPolicy(\n config: StandardRateLimitPolicyConfig,\n): RateLimitPolicy {\n const { remainingHeader, resetHeader, resetUnit, resetFallbackMs } = config;\n const multiplier = resetUnit === 's' ? 1000 : 1;\n return {\n parse(h) {\n const remainingRaw = h.get(remainingHeader);\n if (remainingRaw === null || remainingRaw.trim() === '') {\n return null;\n }\n const remaining = Number(remainingRaw);\n if (!Number.isFinite(remaining)) {\n return null;\n }\n const resetRaw = h.get(resetHeader);\n if (resetRaw === null) {\n if (resetFallbackMs === undefined) {\n return null;\n }\n return {\n remaining,\n resetAt: new Date(Date.now() + resetFallbackMs),\n };\n }\n if (resetRaw.trim() === '') {\n return null;\n }\n const reset = Number(resetRaw);\n if (!Number.isFinite(reset) || reset < 0) {\n return null;\n }\n const resetMs = reset * multiplier;\n if (!Number.isFinite(resetMs)) {\n return null;\n }\n return { remaining, resetAt: new Date(resetMs) };\n },\n };\n}\n","export async function mapWithConcurrency<T, R>(\n items: readonly T[],\n concurrency: number,\n fn: (item: T, index: number) => Promise<R>,\n): Promise<R[]> {\n const results = new Array<R>(items.length);\n if (items.length === 0) {\n return results;\n }\n const normalized = Number.isFinite(concurrency) ? Math.floor(concurrency) : 1;\n const limit = Math.max(1, Math.min(normalized, items.length));\n let next = 0;\n let failed = false;\n\n async function worker(): Promise<void> {\n while (!failed) {\n const i = next++;\n if (i >= items.length) {\n return;\n }\n try {\n results[i] = await fn(items[i]!, i);\n } catch (err) {\n failed = true;\n throw err;\n }\n }\n }\n\n const workers: Promise<void>[] = [];\n for (let w = 0; w < limit; w++) {\n workers.push(worker());\n }\n await Promise.all(workers);\n return results;\n}\n","export interface SanitizeAllowedUrlOptions {\n url: string | null;\n host: string;\n pathname: string;\n protocol?: 'https:' | 'http:';\n}\n\nexport function sanitizeAllowedUrl(\n options: SanitizeAllowedUrlOptions,\n): string | null {\n const { url, host, pathname, protocol = 'https:' } = options;\n if (url === null) {\n return null;\n }\n try {\n const u = new URL(url);\n if (u.protocol !== protocol || u.host !== host || u.pathname !== pathname) {\n return null;\n }\n return u.toString();\n } catch {\n return null;\n }\n}\n","export type EpochUnit = 'ms' | 's' | 'iso';\n\nexport function parseEpoch(\n value: number | string | null | undefined,\n unit: EpochUnit,\n): number | null {\n if (value === null || value === undefined) {\n return null;\n }\n if (unit === 'iso') {\n if (typeof value !== 'string') {\n return null;\n }\n const ms = new Date(value).getTime();\n return Number.isFinite(ms) ? ms : null;\n }\n if (typeof value === 'string' && value.trim() === '') {\n return null;\n }\n const n = typeof value === 'number' ? value : Number(value);\n if (!Number.isFinite(n)) {\n return null;\n }\n const result = unit === 's' ? n * 1000 : n;\n return Number.isFinite(result) ? result : null;\n}\n","import { request } from './request';\nimport type { HttpRequest } from './types';\n\nexport function parseLinkHeader(header: string | null): Record<string, string> {\n if (!header) {\n return {};\n }\n const result: Record<string, string> = {};\n for (const part of header.split(',')) {\n const match = part.match(/<([^>]+)>\\s*;\\s*rel=\"([^\"]+)\"/);\n if (match) {\n result[match[2]!] = match[1]!;\n }\n }\n return result;\n}\n\nexport async function* paginateLink<T>(\n initial: HttpRequest,\n parse: (body: unknown) => T[],\n options: { resource: string },\n): AsyncIterable<T> {\n let next: string | null = initial.url;\n while (next) {\n const res: Awaited<ReturnType<typeof request>> = await request(\n {\n ...initial,\n url: next,\n },\n { resource: options.resource },\n );\n for (const item of parse(res.body)) {\n yield item;\n }\n const links = parseLinkHeader(res.headers.get('link'));\n next = links['next'] ?? null;\n }\n}\n\nexport async function* paginateCursor<T>(\n initial: HttpRequest,\n parse: (body: unknown) => { items: T[]; nextCursor: string | null },\n buildNext: (req: HttpRequest, cursor: string) => HttpRequest,\n options: { resource: string },\n): AsyncIterable<T> {\n let req: HttpRequest = initial;\n while (true) {\n const res = await request(req, { resource: options.resource });\n const { items, nextCursor } = parse(res.body);\n for (const item of items) {\n yield item;\n }\n if (!nextCursor) {\n return;\n }\n req = buildNext(req, nextCursor);\n }\n}\n\nexport async function* paginatePage<T>(\n initial: HttpRequest,\n parse: (body: unknown) => { items: T[]; hasMore: boolean },\n buildPage: (req: HttpRequest, page: number) => HttpRequest,\n options: { resource: string },\n): AsyncIterable<T> {\n let page = 1;\n while (true) {\n const req = page === 1 ? initial : buildPage(initial, page);\n const res = await request(req, { resource: options.resource });\n const { items, hasMore } = parse(res.body);\n for (const item of items) {\n yield item;\n }\n if (!hasMore || items.length === 0) {\n return;\n }\n page++;\n }\n}\n","export type LogFields = Record<string, unknown>;\n\nexport interface ConnectorLogger {\n info(event: string, fields?: LogFields): void;\n warn(event: string, fields?: LogFields): void;\n}\n\nexport interface ConnectorLoggerOptions {\n scope: string;\n}\n\nconst MAX_VALUE_LEN = 120;\n\nfunction truncate(s: string, max = MAX_VALUE_LEN): string {\n if (s.length <= max) {\n return s;\n }\n return `${s.slice(0, max - 1)}…`;\n}\n\nfunction formatValue(value: unknown): string {\n if (value === null) {\n return 'null';\n }\n if (value === undefined) {\n return '';\n }\n if (typeof value === 'number' || typeof value === 'boolean') {\n return String(value);\n }\n if (typeof value === 'string') {\n const t = truncate(value);\n if (/[\\s\"=]/.test(t)) {\n return JSON.stringify(t);\n }\n return t;\n }\n if (typeof value === 'bigint') {\n return value.toString();\n }\n let json: string | undefined;\n try {\n json = JSON.stringify(value);\n } catch {\n json = undefined;\n }\n return truncate(json ?? String(value));\n}\n\nexport function formatLogFields(fields?: LogFields): string {\n if (!fields) {\n return '';\n }\n const parts: string[] = [];\n for (const [k, v] of Object.entries(fields)) {\n if (v === undefined) {\n continue;\n }\n parts.push(`${k}=${formatValue(v)}`);\n }\n return parts.length > 0 ? ` ${parts.join(' ')}` : '';\n}\n\nexport function formatLogLine(\n scope: string,\n event: string,\n fields?: LogFields,\n): string {\n return `[${scope}] ${event}${formatLogFields(fields)}`;\n}\n\nexport function createDefaultConnectorLogger(\n opts: ConnectorLoggerOptions,\n): ConnectorLogger {\n return {\n info(event, fields) {\n console.info(formatLogLine(opts.scope, event, fields));\n },\n warn(event, fields) {\n console.warn(formatLogLine(opts.scope, event, fields));\n },\n };\n}\n\nconst NOOP_LOGGER: ConnectorLogger = {\n info() {},\n warn() {},\n};\n\nexport function noopConnectorLogger(): ConnectorLogger {\n return NOOP_LOGGER;\n}\n","import {\n type HttpResponse,\n connectorUserAgent,\n parseEpoch,\n} from '@rawdash/connector-shared';\nimport {\n BaseConnector,\n type ConnectorContext,\n type ConnectorDoc,\n type CredentialsSchema,\n type MetricSample,\n type StorageHandle,\n type SyncOptions,\n type SyncResult,\n defineConfigFields,\n defineConnectorDoc,\n defineResources,\n makeChunkedCursorGuard,\n metricSample,\n schemasFromResources,\n selectActivePhases,\n} from '@rawdash/core';\nimport { z } from 'zod';\n\nconst ANTHROPIC_API_HOST = 'api.anthropic.com';\nconst ANTHROPIC_API_BASE = `https://${ANTHROPIC_API_HOST}`;\nconst ANTHROPIC_API_VERSION = '2023-06-01';\nconst USAGE_PAGE_LIMIT = 31;\nconst COSTS_PAGE_LIMIT = 31;\nconst MS_PER_DAY = 86_400_000;\nconst DEFAULT_LOOKBACK_DAYS = 30;\nconst INCREMENTAL_LOOKBACK_DAYS = 2;\n// Cost report `amount` is a decimal string in the lowest currency unit (cents\n// for USD): e.g. \"123.45\" represents $1.2345. Divide by 100 to get dollars.\nconst COST_AMOUNT_DIVISOR = 100;\n\nexport const configFields = defineConfigFields(\n z.object({\n adminApiKey: z.object({ $secret: z.string().min(1) }).meta({\n label: 'Admin API key',\n description:\n 'Anthropic organization admin API key (starts with sk-ant-admin-). Create one at console.anthropic.com -> Settings -> Admin keys. Regular API keys (sk-ant-api-) cannot read the Usage and Cost reports.',\n placeholder: 'ANTHROPIC_ADMIN_API_KEY',\n secret: true,\n }),\n workspaceIds: z.array(z.string().min(1)).nonempty().optional().meta({\n label: 'Workspace IDs (optional)',\n description:\n 'Restrict usage and cost queries to specific Anthropic workspace ids (wrkspc_...). Omit to aggregate every workspace the admin key can see.',\n }),\n resources: z\n .array(\n z.enum([\n 'anthropic_input_tokens',\n 'anthropic_output_tokens',\n 'anthropic_cache_read_tokens',\n 'anthropic_cache_creation_tokens',\n 'anthropic_web_search_requests',\n 'anthropic_cost_usd',\n ]),\n )\n .nonempty()\n .optional()\n .meta({\n label: 'Resources',\n description:\n 'Which Anthropic metric series to sync. Omit to sync all of them. The five usage metrics share one upstream call to the Messages Usage Report; enabling any one of them fetches the report and writes all five.',\n }),\n lookbackDays: z.number().int().positive().max(180).optional().meta({\n label: 'Backfill window (days)',\n description:\n 'How many days of usage history to fetch on a full sync. Defaults to 30. The Usage Report returns at most 31 buckets per page, so longer windows paginate.',\n placeholder: '30',\n }),\n }),\n);\n\nexport const doc: ConnectorDoc = defineConnectorDoc({\n displayName: 'Anthropic',\n category: 'engineering',\n brandColor: '#D97757',\n tagline:\n 'Track Anthropic spend, daily token usage across Claude models, cache hit volumes, and web-search tool requests from the Anthropic Admin API.',\n vendor: {\n name: 'Anthropic',\n domain: 'anthropic.com',\n apiDocs:\n 'https://docs.claude.com/en/api/admin-api/usage-cost/get-messages-usage-report',\n website: 'https://anthropic.com',\n },\n auth: {\n summary:\n 'Authenticates with an Anthropic organization admin API key (sk-ant-admin-). Admin keys are the only key class that can read the Usage and Cost reports; regular API keys return 403.',\n setup: [\n 'Open console.anthropic.com -> Settings -> Admin Keys and create a new admin key. Admin keys are organization-scoped, so create the key from the organization whose usage you want to read.',\n 'Store the key as a secret (e.g. ANTHROPIC_ADMIN_API_KEY).',\n 'Reference it from config as `adminApiKey: secret(\"ANTHROPIC_ADMIN_API_KEY\")`.',\n 'Optionally set `workspaceIds` to restrict the query to a subset of workspaces.',\n ],\n },\n rateLimit:\n 'The Admin API returns 429 with a Retry-After header on burst; the shared HTTP client honors it automatically. Daily syncs against the Usage and Cost reports are well below the per-organization Admin API budget.',\n limitations: [\n 'Only the organization Messages Usage Report and Cost Report endpoints are synced. Per-request logs and individual message bodies are not exposed by the Admin API.',\n 'All samples are bucketed daily (1d bucket_width). The Usage Report also supports hourly and per-minute granularity but those are not exposed here in v1.',\n 'The Cost Report only supports 1d bucket_width and reports cost in USD; non-USD billing currencies are not converted.',\n 'Admin API keys are required - regular sk-ant-api- keys do not have access to the organization Usage and Cost reports.',\n ],\n});\n\nconst PHASE_ORDER = ['usage_messages', 'cost_report'] as const;\n\ntype AnthropicPhase = (typeof PHASE_ORDER)[number];\n\nexport type AnthropicResource =\n | 'anthropic_input_tokens'\n | 'anthropic_output_tokens'\n | 'anthropic_cache_read_tokens'\n | 'anthropic_cache_creation_tokens'\n | 'anthropic_web_search_requests'\n | 'anthropic_cost_usd';\n\nconst isAnthropicSyncCursor = makeChunkedCursorGuard(PHASE_ORDER);\n\nconst RESOURCES_BY_PHASE: Record<AnthropicPhase, readonly AnthropicResource[]> =\n {\n usage_messages: [\n 'anthropic_input_tokens',\n 'anthropic_output_tokens',\n 'anthropic_cache_read_tokens',\n 'anthropic_cache_creation_tokens',\n 'anthropic_web_search_requests',\n ],\n cost_report: ['anthropic_cost_usd'],\n };\n\nconst PHASE_ENDPOINT_PATH: Record<AnthropicPhase, string> = {\n usage_messages: '/v1/organizations/usage_report/messages',\n cost_report: '/v1/organizations/cost_report',\n};\n\nconst usageCacheCreationSchema = z.object({\n ephemeral_1h_input_tokens: z.number().nonnegative().nullish(),\n ephemeral_5m_input_tokens: z.number().nonnegative().nullish(),\n});\n\nconst usageServerToolUseSchema = z.object({\n web_search_requests: z.number().int().nonnegative().nullish(),\n});\n\nconst usageResultSchema = z.object({\n account_id: z.string().nullish(),\n api_key_id: z.string().nullish(),\n cache_creation: usageCacheCreationSchema.nullish(),\n cache_read_input_tokens: z.number().nonnegative(),\n context_window: z.string().nullish(),\n inference_geo: z.string().nullish(),\n model: z.string().nullish(),\n output_tokens: z.number().nonnegative(),\n server_tool_use: usageServerToolUseSchema.nullish(),\n service_account_id: z.string().nullish(),\n service_tier: z.string().nullish(),\n uncached_input_tokens: z.number().nonnegative(),\n workspace_id: z.string().nullish(),\n});\n\nconst costResultSchema = z.object({\n amount: z.string(),\n context_window: z.string().nullish(),\n cost_type: z.string().nullish(),\n currency: z.string(),\n description: z.string().nullish(),\n inference_geo: z.string().nullish(),\n model: z.string().nullish(),\n service_tier: z.string().nullish(),\n token_type: z.string().nullish(),\n workspace_id: z.string().nullish(),\n});\n\nfunction bucketResponseSchema<T extends z.ZodTypeAny>(resultSchema: T) {\n return z.object({\n data: z.array(\n z.object({\n starting_at: z.string(),\n ending_at: z.string(),\n results: z.array(resultSchema),\n }),\n ),\n has_more: z.boolean(),\n next_page: z.string().nullish(),\n });\n}\n\nconst usageResponseSchema = bucketResponseSchema(usageResultSchema);\nconst costResponseSchema = bucketResponseSchema(costResultSchema);\n\ntype UsageResult = z.infer<typeof usageResultSchema>;\ntype CostResult = z.infer<typeof costResultSchema>;\n\ninterface BucketPage<TResult> {\n starting_at: string;\n ending_at: string;\n results: TResult[];\n}\n\ninterface PageResponse<TResult> {\n buckets: BucketPage<TResult>[];\n nextPage: string | null;\n}\n\nconst USAGE_DIMENSIONS = [\n {\n name: 'model',\n description: 'Claude model id reported by Anthropic (or null).',\n },\n {\n name: 'workspace_id',\n description:\n 'Anthropic workspace id the usage is attributed to (or null for the default workspace).',\n },\n {\n name: 'api_key_id',\n description: 'API key id the usage is attributed to (or null).',\n },\n {\n name: 'service_tier',\n description:\n 'Service tier the request ran under (standard, batch, priority, flex, etc.), or null.',\n },\n {\n name: 'context_window',\n description:\n 'Context window bucket the request used (0-200k or 200k-1M), or null.',\n },\n {\n name: 'inference_geo',\n description:\n 'Inference geo the request ran in (global, us, not_available), or null.',\n },\n {\n name: 'account_id',\n description: 'Account id the usage is attributed to (or null).',\n },\n {\n name: 'service_account_id',\n description: 'Service account id the usage is attributed to (or null).',\n },\n] as const;\n\nconst COST_DIMENSIONS = [\n {\n name: 'workspace_id',\n description:\n 'Anthropic workspace id the cost is attributed to (or null for the default workspace).',\n },\n {\n name: 'description',\n description:\n 'Human-readable cost line item label (e.g. \"Claude Sonnet 4 Usage - Input Tokens\"), or null when ungrouped.',\n },\n {\n name: 'cost_type',\n description:\n 'Cost category (tokens, web_search, code_execution, session_usage), or null.',\n },\n {\n name: 'model',\n description:\n 'Claude model the cost is attributed to (or null for non-token costs).',\n },\n {\n name: 'token_type',\n description:\n 'Token category for token costs (uncached_input_tokens, output_tokens, cache_read_input_tokens, cache_creation.ephemeral_*_input_tokens), or null.',\n },\n {\n name: 'service_tier',\n description:\n 'Service tier the cost is attributed to (standard or batch), or null.',\n },\n {\n name: 'context_window',\n description:\n 'Context window the cost is attributed to (0-200k or 200k-1M), or null.',\n },\n {\n name: 'currency',\n description:\n 'Billing currency reported by Anthropic (currently always USD).',\n },\n] as const;\n\nexport const anthropicResources = defineResources({\n anthropic_input_tokens: {\n shape: 'metric',\n description:\n 'Daily uncached input tokens processed by the Anthropic Messages API, grouped by model and workspace.',\n endpoint: 'GET /v1/organizations/usage_report/messages',\n unit: 'tokens',\n granularity: 'daily',\n dimensions: [...USAGE_DIMENSIONS],\n notes:\n 'Sample value is uncached_input_tokens. Cache-read and cache-creation token volumes are mirrored on their own metrics so a cache hit ratio can be computed at query time.',\n responses: { usage_messages: usageResponseSchema },\n },\n anthropic_output_tokens: {\n shape: 'metric',\n description:\n 'Daily output tokens generated by the Anthropic Messages API, grouped by model and workspace.',\n endpoint: 'GET /v1/organizations/usage_report/messages',\n unit: 'tokens',\n granularity: 'daily',\n dimensions: [...USAGE_DIMENSIONS],\n notes:\n 'Written alongside anthropic_input_tokens from the same usage_messages API call.',\n },\n anthropic_cache_read_tokens: {\n shape: 'metric',\n description:\n 'Daily input tokens read from the prompt cache, grouped by model and workspace.',\n endpoint: 'GET /v1/organizations/usage_report/messages',\n unit: 'tokens',\n granularity: 'daily',\n dimensions: [...USAGE_DIMENSIONS],\n notes:\n 'Cache hits are charged at a fraction of the uncached rate, so this metric paired with anthropic_input_tokens gives the cache hit ratio.',\n },\n anthropic_cache_creation_tokens: {\n shape: 'metric',\n description:\n 'Daily input tokens written into the prompt cache (sum of the 1h and 5m ephemeral caches), grouped by model and workspace.',\n endpoint: 'GET /v1/organizations/usage_report/messages',\n unit: 'tokens',\n granularity: 'daily',\n dimensions: [...USAGE_DIMENSIONS],\n measures: [\n {\n name: 'ephemeral_1h_input_tokens',\n description:\n 'Input tokens written into the 1-hour ephemeral prompt cache (a component of the sample value).',\n },\n {\n name: 'ephemeral_5m_input_tokens',\n description:\n 'Input tokens written into the 5-minute ephemeral prompt cache (a component of the sample value).',\n },\n ],\n notes:\n 'The per-cache-bucket counts (ephemeral_1h_input_tokens, ephemeral_5m_input_tokens) are declared measures for finer-grained widgets.',\n },\n anthropic_web_search_requests: {\n shape: 'metric',\n description:\n 'Daily count of web-search tool requests executed server-side by Claude, grouped by model and workspace.',\n endpoint: 'GET /v1/organizations/usage_report/messages',\n unit: 'requests',\n granularity: 'daily',\n dimensions: [...USAGE_DIMENSIONS],\n notes:\n 'Sourced from server_tool_use.web_search_requests on each usage bucket. Zero rows are still written so a \"no usage today\" widget renders correctly.',\n },\n anthropic_cost_usd: {\n shape: 'metric',\n description:\n 'Daily organization spend in USD, broken down by workspace and cost line item, pulled from the Anthropic Cost Report.',\n endpoint: 'GET /v1/organizations/cost_report',\n unit: 'USD',\n granularity: 'daily',\n dimensions: [...COST_DIMENSIONS],\n notes:\n 'The Cost Report returns amounts as a decimal string in the lowest currency unit (cents for USD). The connector divides by 100 so the stored metric value is dollars. Costs can be revised for a couple of days after the fact; incremental syncs refetch a short trailing window to pick up adjustments.',\n responses: { cost_report: costResponseSchema },\n },\n});\n\nexport interface AnthropicSettings {\n workspaceIds?: readonly string[];\n resources?: readonly AnthropicResource[];\n lookbackDays?: number;\n}\n\nconst anthropicCredentials = {\n adminApiKey: {\n description: 'Anthropic organization admin API key (sk-ant-admin-...)',\n auth: 'required' as const,\n },\n} satisfies CredentialsSchema;\n\ntype AnthropicCredentials = typeof anthropicCredentials;\n\nexport const id = 'anthropic';\n\ninterface UsageWindow {\n startingAt: string;\n endingAt: string;\n startMs: number;\n endMs: number;\n}\n\nexport function getUsageWindow(\n options: SyncOptions,\n lookbackDays: number,\n now: number = Date.now(),\n): UsageWindow {\n const todayStart = Math.floor(now / MS_PER_DAY) * MS_PER_DAY;\n const endMs = todayStart + MS_PER_DAY;\n\n let days = lookbackDays;\n if (options.mode === 'latest') {\n days = INCREMENTAL_LOOKBACK_DAYS;\n } else if (options.since !== undefined) {\n const sinceMs = parseEpoch(options.since, 'iso');\n if (sinceMs !== null) {\n const elapsed = Math.ceil((now - sinceMs) / MS_PER_DAY);\n days = Math.min(\n Math.max(elapsed + INCREMENTAL_LOOKBACK_DAYS, 1),\n lookbackDays,\n );\n }\n }\n const startMs = endMs - days * MS_PER_DAY;\n return {\n startingAt: new Date(startMs).toISOString(),\n endingAt: new Date(endMs).toISOString(),\n startMs,\n endMs,\n };\n}\n\nfunction resourceToPhase(resource: AnthropicResource): AnthropicPhase {\n for (const phase of PHASE_ORDER) {\n if ((RESOURCES_BY_PHASE[phase] as readonly string[]).includes(resource)) {\n return phase;\n }\n }\n // unreachable - RESOURCES_BY_PHASE covers every AnthropicResource\n throw new Error(`anthropic: unmapped resource ${resource}`);\n}\n\nfunction nullableString(value: string | null | undefined): string | null {\n return value === undefined || value === null ? null : value;\n}\n\ninterface UsageDimensionAttributes {\n model: string | null;\n workspace_id: string | null;\n api_key_id: string | null;\n service_tier: string | null;\n context_window: string | null;\n inference_geo: string | null;\n account_id: string | null;\n service_account_id: string | null;\n}\n\nfunction usageDimensionAttributes(row: UsageResult): UsageDimensionAttributes {\n return {\n model: nullableString(row.model),\n workspace_id: nullableString(row.workspace_id),\n api_key_id: nullableString(row.api_key_id),\n service_tier: nullableString(row.service_tier),\n context_window: nullableString(row.context_window),\n inference_geo: nullableString(row.inference_geo),\n account_id: nullableString(row.account_id),\n service_account_id: nullableString(row.service_account_id),\n };\n}\n\nfunction cacheCreationTotal(row: UsageResult): number {\n const c = row.cache_creation;\n if (!c) {\n return 0;\n }\n return (\n (c.ephemeral_1h_input_tokens ?? 0) + (c.ephemeral_5m_input_tokens ?? 0)\n );\n}\n\nfunction tsFromBucket(bucket: BucketPage<unknown>): number | null {\n return parseEpoch(bucket.starting_at, 'iso');\n}\n\nexport function buildUsageSamples(\n buckets: readonly BucketPage<UsageResult>[],\n): {\n inputTokens: MetricSample[];\n outputTokens: MetricSample[];\n cacheReadTokens: MetricSample[];\n cacheCreationTokens: MetricSample[];\n webSearchRequests: MetricSample[];\n} {\n const inputTokens: MetricSample[] = [];\n const outputTokens: MetricSample[] = [];\n const cacheReadTokens: MetricSample[] = [];\n const cacheCreationTokens: MetricSample[] = [];\n const webSearchRequests: MetricSample[] = [];\n for (const bucket of buckets) {\n const ts = tsFromBucket(bucket);\n if (ts === null) {\n continue;\n }\n for (const row of bucket.results) {\n const common = usageDimensionAttributes(row);\n const cacheCreation = row.cache_creation;\n inputTokens.push(\n metricSample(anthropicResources, 'anthropic_input_tokens', {\n ts,\n value: row.uncached_input_tokens,\n attributes: { ...common },\n }),\n );\n outputTokens.push(\n metricSample(anthropicResources, 'anthropic_output_tokens', {\n ts,\n value: row.output_tokens,\n attributes: { ...common },\n }),\n );\n cacheReadTokens.push(\n metricSample(anthropicResources, 'anthropic_cache_read_tokens', {\n ts,\n value: row.cache_read_input_tokens,\n attributes: { ...common },\n }),\n );\n cacheCreationTokens.push(\n metricSample(anthropicResources, 'anthropic_cache_creation_tokens', {\n ts,\n value: cacheCreationTotal(row),\n attributes: {\n ...common,\n ephemeral_1h_input_tokens:\n cacheCreation?.ephemeral_1h_input_tokens ?? 0,\n ephemeral_5m_input_tokens:\n cacheCreation?.ephemeral_5m_input_tokens ?? 0,\n },\n }),\n );\n webSearchRequests.push(\n metricSample(anthropicResources, 'anthropic_web_search_requests', {\n ts,\n value: row.server_tool_use?.web_search_requests ?? 0,\n attributes: { ...common },\n }),\n );\n }\n }\n return {\n inputTokens,\n outputTokens,\n cacheReadTokens,\n cacheCreationTokens,\n webSearchRequests,\n };\n}\n\nexport function buildCostSamples(\n buckets: readonly BucketPage<CostResult>[],\n): MetricSample[] {\n const samples: MetricSample[] = [];\n for (const bucket of buckets) {\n const ts = tsFromBucket(bucket);\n if (ts === null) {\n continue;\n }\n for (const row of bucket.results) {\n const rawAmount = Number.parseFloat(row.amount);\n const value = Number.isFinite(rawAmount)\n ? rawAmount / COST_AMOUNT_DIVISOR\n : 0;\n samples.push(\n metricSample(anthropicResources, 'anthropic_cost_usd', {\n ts,\n value,\n attributes: {\n workspace_id: nullableString(row.workspace_id),\n description: nullableString(row.description),\n cost_type: nullableString(row.cost_type),\n model: nullableString(row.model),\n token_type: nullableString(row.token_type),\n service_tier: nullableString(row.service_tier),\n context_window: nullableString(row.context_window),\n currency: row.currency,\n },\n }),\n );\n }\n }\n return samples;\n}\n\nexport class AnthropicConnector extends BaseConnector<\n AnthropicSettings,\n AnthropicCredentials\n> {\n static readonly id = id;\n\n static readonly resources = anthropicResources;\n\n static readonly schemas = schemasFromResources(anthropicResources);\n\n static create(input: unknown, ctx?: ConnectorContext): AnthropicConnector {\n const parsed = configFields.parse(input);\n return new AnthropicConnector(\n {\n workspaceIds: parsed.workspaceIds,\n resources: parsed.resources,\n lookbackDays: parsed.lookbackDays,\n },\n { adminApiKey: parsed.adminApiKey },\n ctx,\n );\n }\n\n readonly id = id;\n override readonly credentials = anthropicCredentials;\n\n private buildHeaders(): Record<string, string> {\n return {\n 'X-Api-Key': String(this.creds.adminApiKey),\n 'anthropic-version': ANTHROPIC_API_VERSION,\n 'User-Agent': connectorUserAgent(this.id),\n };\n }\n\n private fetch<T>(\n url: string,\n resource: string,\n signal?: AbortSignal,\n ): Promise<HttpResponse<T>> {\n return this.get<T>(url, {\n resource,\n headers: this.buildHeaders(),\n signal,\n });\n }\n\n private buildInitialUrl(phase: AnthropicPhase, window: UsageWindow): string {\n const url = new URL(`${ANTHROPIC_API_BASE}${PHASE_ENDPOINT_PATH[phase]}`);\n url.searchParams.set('starting_at', window.startingAt);\n url.searchParams.set('ending_at', window.endingAt);\n url.searchParams.set('bucket_width', '1d');\n if (phase === 'usage_messages') {\n url.searchParams.set('limit', String(USAGE_PAGE_LIMIT));\n url.searchParams.append('group_by', 'model');\n url.searchParams.append('group_by', 'workspace_id');\n url.searchParams.append('group_by', 'api_key_id');\n url.searchParams.append('group_by', 'service_tier');\n url.searchParams.append('group_by', 'context_window');\n url.searchParams.append('group_by', 'inference_geo');\n for (const workspaceId of this.settings.workspaceIds ?? []) {\n url.searchParams.append('workspace_ids', workspaceId);\n }\n } else {\n url.searchParams.set('limit', String(COSTS_PAGE_LIMIT));\n url.searchParams.append('group_by', 'workspace_id');\n url.searchParams.append('group_by', 'description');\n }\n return url.toString();\n }\n\n private buildNextUrl(currentUrl: string, nextPage: string): string {\n const url = new URL(currentUrl);\n url.searchParams.set('page', nextPage);\n return url.toString();\n }\n\n private async fetchPhasePage<T>(\n phase: AnthropicPhase,\n schema: z.ZodType<T>,\n initialUrl: string,\n page: string | null,\n signal: AbortSignal | undefined,\n ): Promise<{ url: string; parsed: T; nextUrl: string | null }> {\n const url = page ?? initialUrl;\n const res = await this.fetch<unknown>(url, phase, signal);\n const parsed = schema.parse(res.body);\n const body = parsed as unknown as {\n next_page?: string | null;\n has_more?: boolean;\n };\n const nextPage =\n body.has_more === true &&\n typeof body.next_page === 'string' &&\n body.next_page.length > 0\n ? body.next_page\n : null;\n const nextUrl = nextPage ? this.buildNextUrl(url, nextPage) : null;\n return { url, parsed, nextUrl };\n }\n\n async sync(\n options: SyncOptions,\n storage: StorageHandle,\n signal?: AbortSignal,\n ): Promise<SyncResult> {\n const cursor = isAnthropicSyncCursor(options.cursor)\n ? options.cursor\n : undefined;\n const lookbackDays = this.settings.lookbackDays ?? DEFAULT_LOOKBACK_DAYS;\n const window = getUsageWindow(options, lookbackDays);\n\n const phases = selectActivePhases<AnthropicResource, AnthropicPhase>(\n resourceToPhase,\n PHASE_ORDER,\n this.settings.resources,\n );\n\n const startIdx = cursor ? phases.indexOf(cursor.phase) : 0;\n const resumeIdx = startIdx >= 0 ? startIdx : 0;\n\n for (let i = resumeIdx; i < phases.length; i++) {\n const phase = phases[i]!;\n if (signal?.aborted) {\n return { done: false, cursor: { phase, page: null } };\n }\n const phaseStart = Date.now();\n const initialUrl = this.buildInitialUrl(phase, window);\n let pageUrl: string | null = null;\n let pageCount = 0;\n const buckets: BucketPage<unknown>[] = [];\n\n while (true) {\n if (signal?.aborted) {\n return { done: false, cursor: { phase, page: null } };\n }\n pageCount += 1;\n const { parsed, nextUrl } = await this.fetchAnyPhasePage(\n phase,\n initialUrl,\n pageUrl,\n signal,\n );\n const data = parsed.data as BucketPage<unknown>[];\n buckets.push(...data);\n this.logger.info('fetched page', {\n resource: phase,\n page: pageCount,\n items: data.length,\n });\n if (nextUrl === null) {\n break;\n }\n pageUrl = nextUrl;\n }\n\n await this.writePhase(storage, phase, buckets, window);\n this.logger.info('resource done', {\n resource: phase,\n pages: pageCount,\n items: buckets.length,\n duration_ms: Date.now() - phaseStart,\n });\n }\n\n return { done: true };\n }\n\n private async fetchAnyPhasePage(\n phase: AnthropicPhase,\n initialUrl: string,\n page: string | null,\n signal: AbortSignal | undefined,\n ): Promise<{\n parsed: { data: BucketPage<unknown>[] };\n nextUrl: string | null;\n }> {\n switch (phase) {\n case 'usage_messages':\n return this.fetchPhasePage(\n phase,\n usageResponseSchema,\n initialUrl,\n page,\n signal,\n );\n case 'cost_report':\n return this.fetchPhasePage(\n phase,\n costResponseSchema,\n initialUrl,\n page,\n signal,\n );\n }\n }\n\n private async writePhase(\n storage: StorageHandle,\n phase: AnthropicPhase,\n buckets: BucketPage<unknown>[],\n window: UsageWindow,\n ): Promise<void> {\n const replaceWindow = { start: window.startMs, end: window.endMs };\n switch (phase) {\n case 'usage_messages': {\n const samples = buildUsageSamples(buckets as BucketPage<UsageResult>[]);\n await storage.metrics(samples.inputTokens, {\n names: ['anthropic_input_tokens'],\n replaceWindow,\n });\n await storage.metrics(samples.outputTokens, {\n names: ['anthropic_output_tokens'],\n replaceWindow,\n });\n await storage.metrics(samples.cacheReadTokens, {\n names: ['anthropic_cache_read_tokens'],\n replaceWindow,\n });\n await storage.metrics(samples.cacheCreationTokens, {\n names: ['anthropic_cache_creation_tokens'],\n replaceWindow,\n });\n await storage.metrics(samples.webSearchRequests, {\n names: ['anthropic_web_search_requests'],\n replaceWindow,\n });\n return;\n }\n case 'cost_report': {\n const samples = buildCostSamples(buckets as BucketPage<CostResult>[]);\n await storage.metrics(samples, {\n names: ['anthropic_cost_usd'],\n replaceWindow,\n });\n return;\n }\n }\n }\n}\n\nexport type { PageResponse, BucketPage, UsageResult, CostResult };\n","import { AnthropicConnector } from './anthropic';\n\nexport {\n AnthropicConnector,\n anthropicResources as resources,\n buildCostSamples,\n buildUsageSamples,\n configFields,\n doc,\n getUsageWindow,\n id,\n} from './anthropic';\nexport type {\n AnthropicResource,\n AnthropicSettings,\n BucketPage,\n CostResult,\n PageResponse,\n UsageResult,\n} from './anthropic';\nexport default AnthropicConnector;\n"],"mappings":"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","names":[]}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@rawdash/connector-anthropic",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.28.2",
|
|
4
4
|
"description": "Rawdash connector for Anthropic — daily token usage and spend from the Anthropic Admin API (usage and cost reports)",
|
|
5
5
|
"license": "Apache-2.0",
|
|
6
6
|
"type": "module",
|
|
@@ -24,7 +24,7 @@
|
|
|
24
24
|
},
|
|
25
25
|
"dependencies": {
|
|
26
26
|
"zod": "^4.4.3",
|
|
27
|
-
"@rawdash/core": "0.
|
|
27
|
+
"@rawdash/core": "0.28.2"
|
|
28
28
|
},
|
|
29
29
|
"devDependencies": {
|
|
30
30
|
"fast-check": "^4.8.0",
|