@frugalco/frugal-sdk-node 0.3.0 → 0.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +18 -8
- package/dist/config.d.ts +11 -0
- package/dist/config.d.ts.map +1 -1
- package/dist/config.js +111 -3
- package/dist/config.js.map +1 -1
- package/dist/duplicate-filter.d.ts +46 -0
- package/dist/duplicate-filter.d.ts.map +1 -0
- package/dist/duplicate-filter.js +168 -0
- package/dist/duplicate-filter.js.map +1 -0
- package/dist/instrument.d.ts +32 -0
- package/dist/instrument.d.ts.map +1 -1
- package/dist/instrument.js +226 -19
- package/dist/instrument.js.map +1 -1
- package/dist/otel.d.ts +2 -0
- package/dist/otel.d.ts.map +1 -1
- package/dist/otel.js +17 -2
- package/dist/otel.js.map +1 -1
- package/dist/prompt-analyzer.d.ts +12 -1
- package/dist/prompt-analyzer.d.ts.map +1 -1
- package/dist/prompt-analyzer.js +185 -14
- package/dist/prompt-analyzer.js.map +1 -1
- package/dist/providers.d.ts +1 -0
- package/dist/providers.d.ts.map +1 -1
- package/dist/providers.js +4 -1
- package/dist/providers.js.map +1 -1
- package/dist/semconv.d.ts +10 -0
- package/dist/semconv.d.ts.map +1 -1
- package/dist/semconv.js +21 -0
- package/dist/semconv.js.map +1 -1
- package/dist/streams.d.ts.map +1 -1
- package/dist/streams.js +15 -2
- package/dist/streams.js.map +1 -1
- package/dist/track.d.ts +18 -0
- package/dist/track.d.ts.map +1 -1
- package/dist/track.js +2 -1
- package/dist/track.js.map +1 -1
- package/dist/wrappers/anthropic.d.ts.map +1 -1
- package/dist/wrappers/anthropic.js +9 -1
- package/dist/wrappers/anthropic.js.map +1 -1
- package/dist/wrappers/batch.d.ts +11 -0
- package/dist/wrappers/batch.d.ts.map +1 -0
- package/dist/wrappers/batch.js +338 -0
- package/dist/wrappers/batch.js.map +1 -0
- package/dist/wrappers/openai.d.ts.map +1 -1
- package/dist/wrappers/openai.js +37 -30
- package/dist/wrappers/openai.js.map +1 -1
- package/dist/wrappers/patching.d.ts +10 -0
- package/dist/wrappers/patching.d.ts.map +1 -0
- package/dist/wrappers/patching.js +38 -0
- package/dist/wrappers/patching.js.map +1 -0
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -55,10 +55,16 @@ Set both or the wrapper silently returns the client unmodified:
|
|
|
55
55
|
| `FRUGAL_OTEL_EXPORTER_OTLP_HEADERS` | Export auth headers (e.g. `Authorization=Bearer ...`). Overrides the header built from `FRUGAL_API_KEY`. Falls back to `OTEL_EXPORTER_OTLP_HEADERS`. |
|
|
56
56
|
| `FRUGAL_COMPONENTS` | Comma-separated logical components (e.g. `ai-handler,reco`). When set, each metric is ALSO emitted once per component (tagged `frugal.component`) on top of the base project series, so usage can be viewed for the whole project or split by component. |
|
|
57
57
|
| `FRUGAL_CUSTOM_PROVIDERS` | Comma-separated allowlist of provider names beyond the built-in registry (e.g. `together_ai,fireworks`) that should pass through verbatim on `gen_ai.system` / `gen_ai.provider.name` instead of being coerced to `custom`. Capped at 10 (extras are coerced) to keep label cardinality bounded. |
|
|
58
|
+
| `FRUGAL_CUSTOM_LABEL` | Comma-separated allowlist for the `frugal.custom_label` label on manual track calls (e.g. `billing,search,recs`). A `customLabel` passed to `trackAICall`/`startAICall` outside the first 20 entries (a hard, non-configurable cap) is coerced to `other` with a warn-once diagnostic. `other` is reserved and ignored as an entry. See [Custom call sites](#custom-call-sites). |
|
|
58
59
|
| `FRUGAL_PATH_BLOCK_LIST` | Comma-separated repo-relative paths to skip for caller attribution (see below). |
|
|
59
60
|
| `FRUGAL_METRIC_EXPORT_INTERVAL_MS` | Export cadence in milliseconds. Default `60000`. |
|
|
60
61
|
| `FRUGAL_MAX_CALL_SITES` | Cardinality cap — max distinct `(caller_file, caller_function)` pairs to attribute. Default `500`. Set to `0` to disable. |
|
|
61
62
|
| `FRUGAL_PROMPT_ANALYSIS_SAMPLE_RATE` | Fraction of calls (0..1) that run the lightweight prompt analyzer. Default `0.1`. Set to `0` to disable, `1` for every call. |
|
|
63
|
+
| `FRUGAL_DUPLICATE_REQUEST_DETECTION` | Duplicate-request detection (default **on**): every chat request is canonicalized and hashed in-process to classify `first_seen`/`repeat_seen` — only the 2-value state is exported, never content. Set to `0`/`false`/`no`/`off` to disable the hashing and the `frugal.gen_ai.duplicate_requests` metric entirely. |
|
|
64
|
+
| `FRUGAL_DUPLICATE_WINDOW_SECONDS` | How long a request signature is remembered. Default `3600`, max `86400` (retention is 30–60 min at defaults — see [docs/metrics-reference.md](docs/metrics-reference.md)). |
|
|
65
|
+
| `FRUGAL_DUPLICATE_CAPACITY` | Expected distinct requests per window (Bloom filter sizing). Default `10000`, max `1000000`. Raise if the saturation warning fires. |
|
|
66
|
+
| `FRUGAL_DUPLICATE_FP_RATE` | Target false-positive rate for the duplicate filter. Default `0.01`, max `0.5`. |
|
|
67
|
+
| `FRUGAL_DUPLICATE_GENERATIONS` | Rotating-Bloom generation count. Default `2`, max `16`. |
|
|
62
68
|
| `FRUGAL_DISABLED` | Set to `1` to disable all instrumentation without uninstalling. |
|
|
63
69
|
|
|
64
70
|
## What gets captured
|
|
@@ -68,23 +74,27 @@ Every wrapped call emits OTel metrics tagged with the caller's source file and f
|
|
|
68
74
|
| Metric | Kind | Description | Additional attributes |
|
|
69
75
|
| --------------------------------------- | --------- | ------------------------------------------------------------ | -------------------------------------- |
|
|
70
76
|
| `gen_ai.client.operation.duration` | histogram | Call latency in seconds | — |
|
|
71
|
-
| `gen_ai.client.token.usage` | histogram | Input and output token counts | `gen_ai.token.type`
|
|
72
|
-
| `frugal.gen_ai.calls` | counter | Number of calls per site |
|
|
77
|
+
| `gen_ai.client.token.usage` | histogram | Input and output token counts | `gen_ai.token.type`, `frugal.history_bucket` (chat calls) |
|
|
78
|
+
| `frugal.gen_ai.calls` | counter | Number of calls per site | `frugal.history_bucket` (chat calls), `frugal.structured_output_mode` (calls with a request payload) |
|
|
73
79
|
| `frugal.gen_ai.cache_read_tokens` | histogram | Prompt cache hits | — |
|
|
74
80
|
| `frugal.gen_ai.cache_write_tokens` | histogram | Prompt cache writes | — |
|
|
75
81
|
| `frugal.gen_ai.reasoning_tokens` | histogram | OpenAI reasoning tokens (o-series), Anthropic thinking tokens| — |
|
|
76
82
|
| `frugal.gen_ai.errors` | counter | Errors by exception type | `frugal.error.type` |
|
|
77
83
|
| `frugal.gen_ai.internal_errors` | counter | frugal-metrics internal failures (cardinality cap, bugs) | `frugal.internal_error.stage` |
|
|
78
84
|
| `frugal.gen_ai.output_cap_utilization` | histogram | `output_tokens / max_tokens` ratio when `max_tokens` is set | `frugal.structured_output_mode` |
|
|
79
|
-
| `frugal.gen_ai.history_depth` | histogram | `messages.length` (
|
|
85
|
+
| `frugal.gen_ai.history_depth` | histogram | `messages.length` — every chat call, success or error (100%, not sampled) | — |
|
|
86
|
+
| `frugal.gen_ai.duplicate_requests` | counter | Request-duplication state from a windowed signature filter — every chat call (100%, not sampled; see `FRUGAL_DUPLICATE_*` vars) | `frugal.dup_state` (`first_seen` \| `repeat_seen`) |
|
|
80
87
|
| `frugal.gen_ai.few_shot_count` | histogram | Few-shot exemplar pairs before final user (analyzer-sampled) | — |
|
|
88
|
+
| `frugal.gen_ai.few_shot_tokens` | histogram | Approx. token size of the few-shot example block (analyzer-sampled) | — |
|
|
81
89
|
| `frugal.gen_ai.noise_ratio` | histogram | HTML/base64/whitespace fraction of input (analyzer-sampled) | — |
|
|
82
90
|
| `frugal.gen_ai.cache_prefix_stable` | counter | Per-block prompt-cache stability (analyzer-sampled) | `frugal.block`, `frugal.stable` |
|
|
83
91
|
| `frugal.gen_ai.structured_output_hint` | counter | "Return JSON" prompts missing `response_format`/`tools` | — |
|
|
84
92
|
|
|
85
|
-
The
|
|
86
|
-
(`FRUGAL_PROMPT_ANALYSIS_SAMPLE_RATE`, default 10%)
|
|
87
|
-
|
|
93
|
+
The analyzer-sampled metrics fire on a fraction of calls
|
|
94
|
+
(`FRUGAL_PROMPT_ANALYSIS_SAMPLE_RATE`, default 10%). Everything else —
|
|
95
|
+
including `history_depth`, the `frugal.history_bucket` label on `calls` and
|
|
96
|
+
`token.usage`, the `frugal.structured_output_mode` label on `calls`, and
|
|
97
|
+
`duplicate_requests` — is 100%, never sampled.
|
|
88
98
|
|
|
89
99
|
Every metric carries this base attribute set:
|
|
90
100
|
|
|
@@ -93,7 +103,7 @@ Every metric carries this base attribute set:
|
|
|
93
103
|
- `gen_ai.system` + `gen_ai.provider.name` — the provider, dual-emitted under both the legacy and current OTel attribute (`openai`, `anthropic`, `aws.bedrock`, `vertex_ai`/`gcp.vertex_ai`, …, or `custom`). Override via the `provider` field; an unrecognized value is coerced to `custom` with a one-time warning. See [`docs/metrics-reference.md`](docs/metrics-reference.md#attributes).
|
|
94
104
|
- `gen_ai.request.model`, `gen_ai.response.model` — the model used
|
|
95
105
|
- `gen_ai.operation.name` — `chat`, `text_completion`, `embeddings`, `generate_content`, `execute_tool`, `create_agent`, `invoke_agent`, or `other` (the sentinel an unrecognized operation coerces to)
|
|
96
|
-
- `frugal.batched` — `true` for batch-API submissions, `false` otherwise
|
|
106
|
+
- `frugal.batched` — `true` for batch-API paths (OpenAI Batch API submissions; Anthropic Message Batches submissions and results), `false` otherwise
|
|
97
107
|
|
|
98
108
|
The "Additional attributes" column above lists what each metric carries on top of the base set. See [`docs/metrics-reference.md`](docs/metrics-reference.md) for the values each attribute takes, sentinel values for caller fields, and the full internal-error-stage list.
|
|
99
109
|
|
|
@@ -161,7 +171,7 @@ try {
|
|
|
161
171
|
|
|
162
172
|
`end()` and `fail()` finalize the call exactly once and are idempotent — extra calls are ignored, so a `try/finally` that always calls `end()` won't double-count.
|
|
163
173
|
|
|
164
|
-
**Metadata** — `info` (all optional): `provider` (→ `gen_ai.system`, default `"custom"`), `operation` (default `"chat"`), `model` (→ `gen_ai.request.model`, default `"<unknown>"`). To also run the sampled prompt analyzer, pass any of `messages` / `system` / `tools` / `responseFormat` / `maxTokens`.
|
|
174
|
+
**Metadata** — `info` (all optional): `provider` (→ `gen_ai.system`, default `"custom"`), `operation` (default `"chat"`), `model` (→ `gen_ai.request.model`, default `"<unknown>"`), `customLabel` (→ `frugal.custom_label` on **every** metric of the call; the value must be one of the first 20 comma-separated entries of `FRUGAL_CUSTOM_LABEL` or it is coerced to `"other"` — the label is exclusive to this manual API, wrapper calls never carry it). To also run the sampled prompt analyzer, pass any of `messages` / `system` / `tools` / `responseFormat` / `maxTokens`.
|
|
165
175
|
|
|
166
176
|
**Usage** — `report(...)` / `end(...)` fields (all optional): `inputTokens`, `outputTokens`, `cacheReadTokens`, `cacheWriteTokens`, `reasoningTokens`, `responseModel`.
|
|
167
177
|
|
package/dist/config.d.ts
CHANGED
|
@@ -11,6 +11,12 @@ export interface Config {
|
|
|
11
11
|
* keep `gen_ai.system` / `gen_ai.provider.name` cardinality bounded.
|
|
12
12
|
*/
|
|
13
13
|
customProviders: readonly string[];
|
|
14
|
+
/**
|
|
15
|
+
* Allowlist for the `frugal.custom_label` label on manual track calls. A
|
|
16
|
+
* `customLabel` value outside this list is coerced to `other` at emit time,
|
|
17
|
+
* so the label's cardinality is bounded by MAX_CUSTOM_LABELS + 1.
|
|
18
|
+
*/
|
|
19
|
+
customLabels: readonly string[];
|
|
14
20
|
repoRoot: string;
|
|
15
21
|
pathBlockList: readonly string[];
|
|
16
22
|
otlpEndpoint: string;
|
|
@@ -20,6 +26,11 @@ export interface Config {
|
|
|
20
26
|
maxCallSites: number;
|
|
21
27
|
/** Fraction of analyzed calls to emit prompt-analysis metrics for, 0..1. */
|
|
22
28
|
promptAnalysisSampleRate: number;
|
|
29
|
+
duplicateDetection: boolean;
|
|
30
|
+
duplicateWindowSeconds: number;
|
|
31
|
+
duplicateCapacity: number;
|
|
32
|
+
duplicateFpRate: number;
|
|
33
|
+
duplicateGenerations: number;
|
|
23
34
|
}
|
|
24
35
|
export declare function loadConfig(): Config | null;
|
|
25
36
|
/** Test-only hook to re-arm the one-shot warning. */
|
package/dist/config.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"config.d.ts","sourceRoot":"","sources":["../src/config.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"config.d.ts","sourceRoot":"","sources":["../src/config.ts"],"names":[],"mappings":"AAmCA,MAAM,WAAW,MAAM;IACrB,mGAAmG;IACnG,UAAU,EAAE,MAAM,GAAG,IAAI,CAAC;IAC1B,+EAA+E;IAC/E,SAAS,EAAE,MAAM,CAAC;IAClB,sGAAsG;IACtG,UAAU,EAAE,SAAS,MAAM,EAAE,CAAC;IAC9B;;;;OAIG;IACH,eAAe,EAAE,SAAS,MAAM,EAAE,CAAC;IACnC;;;;OAIG;IACH,YAAY,EAAE,SAAS,MAAM,EAAE,CAAC;IAChC,QAAQ,EAAE,MAAM,CAAC;IACjB,aAAa,EAAE,SAAS,MAAM,EAAE,CAAC;IACjC,YAAY,EAAE,MAAM,CAAC;IACrB,WAAW,EAAE,MAAM,CAAC;IACpB,gBAAgB,EAAE,MAAM,CAAC;IACzB,8FAA8F;IAC9F,YAAY,EAAE,MAAM,CAAC;IACrB,4EAA4E;IAC5E,wBAAwB,EAAE,MAAM,CAAC;IACjC,kBAAkB,EAAE,OAAO,CAAC;IAC5B,sBAAsB,EAAE,MAAM,CAAC;IAC/B,iBAAiB,EAAE,MAAM,CAAC;IAC1B,eAAe,EAAE,MAAM,CAAC;IACxB,oBAAoB,EAAE,MAAM,CAAC;CAC9B;AAqHD,wBAAgB,UAAU,IAAI,MAAM,GAAG,IAAI,CA+O1C;AAED,qDAAqD;AACrD,wBAAgB,yBAAyB,IAAI,IAAI,CAEhD"}
|
package/dist/config.js
CHANGED
|
@@ -29,6 +29,7 @@
|
|
|
29
29
|
import { realpathSync } from "node:fs";
|
|
30
30
|
import { resolve } from "node:path";
|
|
31
31
|
import { emitLog, emitLogOnce } from "./debug.js";
|
|
32
|
+
import { CUSTOM_LABEL_OTHER } from "./semconv.js";
|
|
32
33
|
const ENV = {
|
|
33
34
|
CUSTOMER_ID: "FRUGAL_CUSTOMER_ID",
|
|
34
35
|
PROJECT_ID: "FRUGAL_PROJECT_ID",
|
|
@@ -39,6 +40,7 @@ const ENV = {
|
|
|
39
40
|
METRICS_API_KEY: "FRUGAL_METRICS_API_KEY",
|
|
40
41
|
COMPONENTS: "FRUGAL_COMPONENTS",
|
|
41
42
|
CUSTOM_PROVIDERS: "FRUGAL_CUSTOM_PROVIDERS",
|
|
43
|
+
CUSTOM_LABEL: "FRUGAL_CUSTOM_LABEL",
|
|
42
44
|
REPO_ROOT: "FRUGAL_REPO_ROOT",
|
|
43
45
|
PATH_BLOCK_LIST: "FRUGAL_PATH_BLOCK_LIST",
|
|
44
46
|
DISABLED: "FRUGAL_DISABLED",
|
|
@@ -50,12 +52,43 @@ const ENV = {
|
|
|
50
52
|
EXPORT_INTERVAL_MS: "FRUGAL_METRIC_EXPORT_INTERVAL_MS",
|
|
51
53
|
MAX_CALL_SITES: "FRUGAL_MAX_CALL_SITES",
|
|
52
54
|
PROMPT_ANALYSIS_SAMPLE_RATE: "FRUGAL_PROMPT_ANALYSIS_SAMPLE_RATE",
|
|
55
|
+
DUPLICATE_DETECTION: "FRUGAL_DUPLICATE_REQUEST_DETECTION",
|
|
56
|
+
DUPLICATE_WINDOW_SECONDS: "FRUGAL_DUPLICATE_WINDOW_SECONDS",
|
|
57
|
+
DUPLICATE_CAPACITY: "FRUGAL_DUPLICATE_CAPACITY",
|
|
58
|
+
DUPLICATE_FP_RATE: "FRUGAL_DUPLICATE_FP_RATE",
|
|
59
|
+
DUPLICATE_GENERATIONS: "FRUGAL_DUPLICATE_GENERATIONS",
|
|
53
60
|
};
|
|
54
61
|
const DEFAULT_OTLP_ENDPOINT = "https://metrics.frugal.co/opentelemetry/v1/metrics";
|
|
55
62
|
const DEFAULT_MAX_CALL_SITES = 500;
|
|
56
63
|
const DEFAULT_PROMPT_ANALYSIS_SAMPLE_RATE = 0.1;
|
|
64
|
+
const DEFAULT_DUPLICATE_WINDOW_SECONDS = 3600;
|
|
65
|
+
const DEFAULT_DUPLICATE_CAPACITY = 10_000;
|
|
66
|
+
const DEFAULT_DUPLICATE_FP_RATE = 0.01;
|
|
67
|
+
const DEFAULT_DUPLICATE_GENERATIONS = 2;
|
|
68
|
+
// Bloom memory is ~(1.44·log2(1/p))/8 bytes × capacity × generations — the fp
|
|
69
|
+
// rate matters: ~1.2 B/entry at the 0.01 default (≈24 KB total at defaults)
|
|
70
|
+
// but ~3.6 B/entry at the 1e-6 floor the filter clamps to. The caps below
|
|
71
|
+
// bound the worst case to ≈58 MB (capacity 1e6 × 16 generations × 3.6 B),
|
|
72
|
+
// allocated inside the customer's first call — large, but a knowable ceiling
|
|
73
|
+
// rather than a fat-fingered env var demanding gigabytes. Saturation checks
|
|
74
|
+
// popcount the full allocation every 1024th insert, so the ceiling also bounds
|
|
75
|
+
// that hot-path cost.
|
|
76
|
+
const MAX_DUPLICATE_CAPACITY = 1_000_000;
|
|
77
|
+
const MAX_DUPLICATE_GENERATIONS = 16;
|
|
78
|
+
// Above the documented window there is no additional duplicate signal — only a
|
|
79
|
+
// filter that effectively never rotates and saturates for the process
|
|
80
|
+
// lifetime. One day is already far beyond any retry/cache horizon.
|
|
81
|
+
const MAX_DUPLICATE_WINDOW_SECONDS = 86_400;
|
|
82
|
+
// bloomParams clamps fp to at most 0.5 (the mathematical ceiling where the
|
|
83
|
+
// filter degenerates); values in [0.5, 1) must warn HERE, not silently differ
|
|
84
|
+
// from what the filter actually uses.
|
|
85
|
+
const MAX_DUPLICATE_FP_RATE = 0.5;
|
|
57
86
|
/** Upper bound on customer-registered provider names — keeps label cardinality bounded. */
|
|
58
87
|
const MAX_CUSTOM_PROVIDERS = 10;
|
|
88
|
+
// Hard bound on the frugal.custom_label allowlist. Deliberately a
|
|
89
|
+
// module-private literal with no env override: the cap is the cardinality
|
|
90
|
+
// contract for the label, so it must not be user-tunable.
|
|
91
|
+
const MAX_CUSTOM_LABELS = 20;
|
|
59
92
|
let warnedOnce = false;
|
|
60
93
|
function truthy(value) {
|
|
61
94
|
if (!value)
|
|
@@ -63,6 +96,32 @@ function truthy(value) {
|
|
|
63
96
|
const v = value.trim().toLowerCase();
|
|
64
97
|
return v === "1" || v === "true" || v === "yes" || v === "on";
|
|
65
98
|
}
|
|
99
|
+
function explicitlyOff(value) {
|
|
100
|
+
if (value === undefined)
|
|
101
|
+
return false;
|
|
102
|
+
const v = value.trim().toLowerCase();
|
|
103
|
+
return v === "0" || v === "false" || v === "no" || v === "off";
|
|
104
|
+
}
|
|
105
|
+
// Number(raw) validates the FULL string (unlike parseInt/parseFloat, whose
|
|
106
|
+
// prefix parsing turns "1e6" into 1 and "10,000" into 10 — a capacity of 1
|
|
107
|
+
// means a 10-bit filter that saturates instantly). Warnings are de-duped per
|
|
108
|
+
// var: loadConfig() runs on every call, so a bad value must not flood the
|
|
109
|
+
// bounded debug ring buffer.
|
|
110
|
+
function parsePositive(raw, name, fallback, { integer = false, max } = {}) {
|
|
111
|
+
if (!raw || !raw.trim())
|
|
112
|
+
return fallback;
|
|
113
|
+
const parsed = Number(raw.trim());
|
|
114
|
+
const valid = Number.isFinite(parsed) && parsed > 0 && (!integer || Number.isInteger(parsed));
|
|
115
|
+
if (!valid) {
|
|
116
|
+
emitLogOnce(`bad_env_${name}`, "warn", `could not parse ${name}=${raw}; using default ${fallback}`);
|
|
117
|
+
return fallback;
|
|
118
|
+
}
|
|
119
|
+
if (max !== undefined && parsed > max) {
|
|
120
|
+
emitLogOnce(`clamped_env_${name}`, "warn", `${name}=${raw} exceeds the maximum ${max}; clamping to ${max}`);
|
|
121
|
+
return max;
|
|
122
|
+
}
|
|
123
|
+
return parsed;
|
|
124
|
+
}
|
|
66
125
|
function warnOnce(message) {
|
|
67
126
|
if (warnedOnce)
|
|
68
127
|
return;
|
|
@@ -137,6 +196,29 @@ export function loadConfig() {
|
|
|
137
196
|
if (customProvidersAll.length > MAX_CUSTOM_PROVIDERS) {
|
|
138
197
|
emitLogOnce("custom_providers_truncated", "warn", `${ENV.CUSTOM_PROVIDERS} lists ${customProvidersAll.length} entries; only the first ${MAX_CUSTOM_PROVIDERS} are honored, the rest are coerced to "custom".`);
|
|
139
198
|
}
|
|
199
|
+
// Optional comma-separated allowlist for the frugal.custom_label attribute on
|
|
200
|
+
// manual track calls. Only the first MAX_CUSTOM_LABELS comma-separated
|
|
201
|
+
// entries are honored (sliced BEFORE trim/dedupe — the position in the raw
|
|
202
|
+
// list is the contract); customLabel values outside the allowlist are
|
|
203
|
+
// coerced to "other" at emit time.
|
|
204
|
+
const customLabelsRaw = process.env[ENV.CUSTOM_LABEL] ?? "";
|
|
205
|
+
const customLabelEntries = customLabelsRaw.split(",");
|
|
206
|
+
const customLabelsParsed = [
|
|
207
|
+
...new Set(customLabelEntries
|
|
208
|
+
.slice(0, MAX_CUSTOM_LABELS)
|
|
209
|
+
.map((e) => e.trim())
|
|
210
|
+
.filter((e) => e.length > 0)),
|
|
211
|
+
];
|
|
212
|
+
// "other" is the coercion sentinel, never a legitimate allowlist entry: a
|
|
213
|
+
// real unit named "other" would inseparably absorb every non-allowlisted
|
|
214
|
+
// value from unrelated call sites.
|
|
215
|
+
const customLabels = customLabelsParsed.filter((e) => e !== CUSTOM_LABEL_OTHER);
|
|
216
|
+
if (customLabels.length < customLabelsParsed.length) {
|
|
217
|
+
emitLogOnce("custom_labels_reserved", "warn", `${ENV.CUSTOM_LABEL} contains the reserved value "${CUSTOM_LABEL_OTHER}" (the coercion sentinel); it is ignored — rename that label to keep it distinguishable from coerced values.`);
|
|
218
|
+
}
|
|
219
|
+
if (customLabelEntries.length > MAX_CUSTOM_LABELS && customLabelsRaw.trim()) {
|
|
220
|
+
emitLogOnce("custom_labels_truncated", "warn", `${ENV.CUSTOM_LABEL} lists ${customLabelEntries.length} entries; only the first ${MAX_CUSTOM_LABELS} are honored, values outside them are coerced to "${CUSTOM_LABEL_OTHER}".`);
|
|
221
|
+
}
|
|
140
222
|
const otlpEndpoint = process.env[ENV.FRUGAL_OTLP_ENDPOINT] ??
|
|
141
223
|
process.env[ENV.OTLP_ENDPOINT] ??
|
|
142
224
|
DEFAULT_OTLP_ENDPOINT;
|
|
@@ -148,7 +230,7 @@ export function loadConfig() {
|
|
|
148
230
|
exportIntervalMs = parsed;
|
|
149
231
|
}
|
|
150
232
|
else {
|
|
151
|
-
|
|
233
|
+
emitLogOnce(`bad_env_${ENV.EXPORT_INTERVAL_MS}`, "warn", `could not parse ${ENV.EXPORT_INTERVAL_MS}=${intervalRaw}; using default ${exportIntervalMs}`);
|
|
152
234
|
}
|
|
153
235
|
}
|
|
154
236
|
const maxCallSitesRaw = process.env[ENV.MAX_CALL_SITES];
|
|
@@ -159,7 +241,7 @@ export function loadConfig() {
|
|
|
159
241
|
maxCallSites = parsed;
|
|
160
242
|
}
|
|
161
243
|
else {
|
|
162
|
-
|
|
244
|
+
emitLogOnce(`bad_env_${ENV.MAX_CALL_SITES}`, "warn", `could not parse ${ENV.MAX_CALL_SITES}=${maxCallSitesRaw}; using default ${maxCallSites}`);
|
|
163
245
|
}
|
|
164
246
|
}
|
|
165
247
|
const sampleRateRaw = process.env[ENV.PROMPT_ANALYSIS_SAMPLE_RATE];
|
|
@@ -170,14 +252,35 @@ export function loadConfig() {
|
|
|
170
252
|
promptAnalysisSampleRate = parsed;
|
|
171
253
|
}
|
|
172
254
|
else {
|
|
173
|
-
|
|
255
|
+
emitLogOnce(`bad_env_${ENV.PROMPT_ANALYSIS_SAMPLE_RATE}`, "warn", `could not parse ${ENV.PROMPT_ANALYSIS_SAMPLE_RATE}=${sampleRateRaw}; using default ${promptAnalysisSampleRate}`);
|
|
174
256
|
}
|
|
175
257
|
}
|
|
258
|
+
const fpRate = parsePositive(process.env[ENV.DUPLICATE_FP_RATE], ENV.DUPLICATE_FP_RATE, DEFAULT_DUPLICATE_FP_RATE);
|
|
259
|
+
let duplicateFpRate = fpRate;
|
|
260
|
+
if (fpRate >= 1) {
|
|
261
|
+
emitLogOnce(`bad_range_${ENV.DUPLICATE_FP_RATE}`, "warn", `${ENV.DUPLICATE_FP_RATE}=${fpRate} must be in (0, 1); using default ${DEFAULT_DUPLICATE_FP_RATE}`);
|
|
262
|
+
duplicateFpRate = DEFAULT_DUPLICATE_FP_RATE;
|
|
263
|
+
}
|
|
264
|
+
else if (fpRate > MAX_DUPLICATE_FP_RATE) {
|
|
265
|
+
emitLogOnce(`clamped_env_${ENV.DUPLICATE_FP_RATE}`, "warn", `${ENV.DUPLICATE_FP_RATE}=${fpRate} exceeds the maximum ${MAX_DUPLICATE_FP_RATE}; clamping to ${MAX_DUPLICATE_FP_RATE}`);
|
|
266
|
+
duplicateFpRate = MAX_DUPLICATE_FP_RATE;
|
|
267
|
+
}
|
|
268
|
+
// The kill switch disables only on an explicit off value. Operators reach
|
|
269
|
+
// for this knob on privacy/overhead grounds, so a value we don't recognize
|
|
270
|
+
// ("disabled", "n", …) must not silently leave full-payload hashing on.
|
|
271
|
+
const dupDetectionRaw = process.env[ENV.DUPLICATE_DETECTION];
|
|
272
|
+
if (dupDetectionRaw !== undefined &&
|
|
273
|
+
dupDetectionRaw.trim() !== "" &&
|
|
274
|
+
!explicitlyOff(dupDetectionRaw) &&
|
|
275
|
+
!truthy(dupDetectionRaw)) {
|
|
276
|
+
emitLogOnce(`bad_env_${ENV.DUPLICATE_DETECTION}`, "warn", `unrecognized ${ENV.DUPLICATE_DETECTION}=${dupDetectionRaw}; duplicate detection remains ENABLED. Use 0/false/no/off to disable.`);
|
|
277
|
+
}
|
|
176
278
|
return {
|
|
177
279
|
customerId,
|
|
178
280
|
projectId: projectId,
|
|
179
281
|
components,
|
|
180
282
|
customProviders,
|
|
283
|
+
customLabels,
|
|
181
284
|
repoRoot: repoRootAbs,
|
|
182
285
|
pathBlockList,
|
|
183
286
|
otlpEndpoint,
|
|
@@ -185,6 +288,11 @@ export function loadConfig() {
|
|
|
185
288
|
exportIntervalMs,
|
|
186
289
|
maxCallSites,
|
|
187
290
|
promptAnalysisSampleRate,
|
|
291
|
+
duplicateDetection: !explicitlyOff(process.env[ENV.DUPLICATE_DETECTION]),
|
|
292
|
+
duplicateWindowSeconds: parsePositive(process.env[ENV.DUPLICATE_WINDOW_SECONDS], ENV.DUPLICATE_WINDOW_SECONDS, DEFAULT_DUPLICATE_WINDOW_SECONDS, { max: MAX_DUPLICATE_WINDOW_SECONDS }),
|
|
293
|
+
duplicateCapacity: parsePositive(process.env[ENV.DUPLICATE_CAPACITY], ENV.DUPLICATE_CAPACITY, DEFAULT_DUPLICATE_CAPACITY, { integer: true, max: MAX_DUPLICATE_CAPACITY }),
|
|
294
|
+
duplicateFpRate,
|
|
295
|
+
duplicateGenerations: parsePositive(process.env[ENV.DUPLICATE_GENERATIONS], ENV.DUPLICATE_GENERATIONS, DEFAULT_DUPLICATE_GENERATIONS, { integer: true, max: MAX_DUPLICATE_GENERATIONS }),
|
|
188
296
|
};
|
|
189
297
|
}
|
|
190
298
|
/** Test-only hook to re-arm the one-shot warning. */
|
package/dist/config.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"config.js","sourceRoot":"","sources":["../src/config.ts"],"names":[],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"config.js","sourceRoot":"","sources":["../src/config.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
import type { Config } from "./config.js";
|
|
2
|
+
import { DUP_FIRST_SEEN, DUP_REPEAT_SEEN } from "./semconv.js";
|
|
3
|
+
export type DupState = typeof DUP_FIRST_SEEN | typeof DUP_REPEAT_SEEN;
|
|
4
|
+
export declare class RotatingBloomFilter {
|
|
5
|
+
private readonly m;
|
|
6
|
+
private readonly k;
|
|
7
|
+
private readonly generations;
|
|
8
|
+
private readonly rotateMs;
|
|
9
|
+
private readonly bytesPerGen;
|
|
10
|
+
private bits;
|
|
11
|
+
private activeIndex;
|
|
12
|
+
private genStartMs;
|
|
13
|
+
private disabled;
|
|
14
|
+
private insertsSinceCheck;
|
|
15
|
+
private saturationWarned;
|
|
16
|
+
constructor(opts: {
|
|
17
|
+
capacity: number;
|
|
18
|
+
fpRate: number;
|
|
19
|
+
generations: number;
|
|
20
|
+
windowSeconds: number;
|
|
21
|
+
});
|
|
22
|
+
/**
|
|
23
|
+
* Classify a signature and insert it. Returns null when the filter is
|
|
24
|
+
* disabled (bit-array allocation failed — latched for the process lifetime
|
|
25
|
+
* so the allocation is not retried on every call).
|
|
26
|
+
*
|
|
27
|
+
* The clock defaults to performance.now() (monotonic): rotation must not
|
|
28
|
+
* freeze when NTP steps the wall clock backward.
|
|
29
|
+
*/
|
|
30
|
+
seenBefore(signature: string, nowMs?: number): DupState | null;
|
|
31
|
+
clear(): void;
|
|
32
|
+
/**
|
|
33
|
+
* Warn (once per fill, re-armed by clear()) when the filter is saturating.
|
|
34
|
+
* Membership tests the UNION of all generations, so occupancy is measured on
|
|
35
|
+
* the union too — measuring only the active generation would let two
|
|
36
|
+
* generations at 49% (union ~74%) pass silently. Past ~50% union fill the
|
|
37
|
+
* effective false-positive rate is far above the configured target and the
|
|
38
|
+
* metric drifts toward all-repeat_seen.
|
|
39
|
+
*/
|
|
40
|
+
private maybeWarnSaturation;
|
|
41
|
+
private rotate;
|
|
42
|
+
private positions;
|
|
43
|
+
}
|
|
44
|
+
export declare function getSharedDuplicateFilter(config: Config): RotatingBloomFilter | null;
|
|
45
|
+
export declare function resetSharedDuplicateFilterForTests(): void;
|
|
46
|
+
//# sourceMappingURL=duplicate-filter.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"duplicate-filter.d.ts","sourceRoot":"","sources":["../src/duplicate-filter.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,aAAa,CAAC;AAE1C,OAAO,EAAE,cAAc,EAAE,eAAe,EAAE,MAAM,cAAc,CAAC;AAE/D,MAAM,MAAM,QAAQ,GAAG,OAAO,cAAc,GAAG,OAAO,eAAe,CAAC;AAyBtE,qBAAa,mBAAmB;IAC9B,OAAO,CAAC,QAAQ,CAAC,CAAC,CAAS;IAC3B,OAAO,CAAC,QAAQ,CAAC,CAAC,CAAS;IAC3B,OAAO,CAAC,QAAQ,CAAC,WAAW,CAAS;IACrC,OAAO,CAAC,QAAQ,CAAC,QAAQ,CAAS;IAClC,OAAO,CAAC,QAAQ,CAAC,WAAW,CAAS;IACrC,OAAO,CAAC,IAAI,CAA6B;IACzC,OAAO,CAAC,WAAW,CAAK;IACxB,OAAO,CAAC,UAAU,CAAK;IACvB,OAAO,CAAC,QAAQ,CAAS;IACzB,OAAO,CAAC,iBAAiB,CAAK;IAC9B,OAAO,CAAC,gBAAgB,CAAS;gBAErB,IAAI,EAAE;QAChB,QAAQ,EAAE,MAAM,CAAC;QACjB,MAAM,EAAE,MAAM,CAAC;QACf,WAAW,EAAE,MAAM,CAAC;QACpB,aAAa,EAAE,MAAM,CAAC;KACvB;IASD;;;;;;;OAOG;IACH,UAAU,CAAC,SAAS,EAAE,MAAM,EAAE,KAAK,GAAE,MAA0B,GAAG,QAAQ,GAAG,IAAI;IAwDjF,KAAK,IAAI,IAAI;IAQb;;;;;;;OAOG;IACH,OAAO,CAAC,mBAAmB;IAqB3B,OAAO,CAAC,MAAM;IAYd,OAAO,CAAC,SAAS;CASlB;AAID,wBAAgB,wBAAwB,CAAC,MAAM,EAAE,MAAM,GAAG,mBAAmB,GAAG,IAAI,CAWnF;AAED,wBAAgB,kCAAkC,IAAI,IAAI,CAEzD"}
|
|
@@ -0,0 +1,168 @@
|
|
|
1
|
+
import { emitLog, emitLogOnce } from "./debug.js";
|
|
2
|
+
import { DUP_FIRST_SEEN, DUP_REPEAT_SEEN } from "./semconv.js";
|
|
3
|
+
function bloomParams(capacity, fpRate) {
|
|
4
|
+
const N = Math.max(1, Math.floor(capacity));
|
|
5
|
+
const p = Math.min(Math.max(fpRate, 1e-6), 0.5);
|
|
6
|
+
const ln2 = Math.LN2;
|
|
7
|
+
const m = Math.max(8, Math.ceil((-N * Math.log(p)) / (ln2 * ln2)));
|
|
8
|
+
const k = Math.max(1, Math.round((m / N) * ln2));
|
|
9
|
+
return { m, k };
|
|
10
|
+
}
|
|
11
|
+
// How often (in insertions) to measure occupancy for the saturation warning.
|
|
12
|
+
// Counting set bits is O(m/8) bytes, so amortizing over 1024 inserts keeps the
|
|
13
|
+
// check negligible even at the capacity clamp.
|
|
14
|
+
const SATURATION_CHECK_INTERVAL = 1024;
|
|
15
|
+
// Union occupancy at which the false-positive rate is degraded badly enough to
|
|
16
|
+
// warn: at 50% fill the FP rate is ~(0.5)^k, orders of magnitude above the
|
|
17
|
+
// configured target, and rising fast.
|
|
18
|
+
const SATURATION_FILL_THRESHOLD = 0.5;
|
|
19
|
+
const POPCOUNT = new Uint8Array(256);
|
|
20
|
+
for (let i = 0; i < 256; i++) {
|
|
21
|
+
POPCOUNT[i] = (i & 1) + POPCOUNT[i >> 1];
|
|
22
|
+
}
|
|
23
|
+
export class RotatingBloomFilter {
|
|
24
|
+
m;
|
|
25
|
+
k;
|
|
26
|
+
generations;
|
|
27
|
+
rotateMs;
|
|
28
|
+
bytesPerGen;
|
|
29
|
+
bits = null;
|
|
30
|
+
activeIndex = 0;
|
|
31
|
+
genStartMs = 0;
|
|
32
|
+
disabled = false;
|
|
33
|
+
insertsSinceCheck = 0;
|
|
34
|
+
saturationWarned = false;
|
|
35
|
+
constructor(opts) {
|
|
36
|
+
const { m, k } = bloomParams(opts.capacity, opts.fpRate);
|
|
37
|
+
this.m = m;
|
|
38
|
+
this.k = k;
|
|
39
|
+
this.generations = Math.max(1, Math.floor(opts.generations));
|
|
40
|
+
this.rotateMs = Math.max(1, (opts.windowSeconds * 1000) / this.generations);
|
|
41
|
+
this.bytesPerGen = Math.ceil(m / 8);
|
|
42
|
+
}
|
|
43
|
+
/**
|
|
44
|
+
* Classify a signature and insert it. Returns null when the filter is
|
|
45
|
+
* disabled (bit-array allocation failed — latched for the process lifetime
|
|
46
|
+
* so the allocation is not retried on every call).
|
|
47
|
+
*
|
|
48
|
+
* The clock defaults to performance.now() (monotonic): rotation must not
|
|
49
|
+
* freeze when NTP steps the wall clock backward.
|
|
50
|
+
*/
|
|
51
|
+
seenBefore(signature, nowMs = performance.now()) {
|
|
52
|
+
if (this.disabled)
|
|
53
|
+
return null;
|
|
54
|
+
if (this.bits === null) {
|
|
55
|
+
try {
|
|
56
|
+
this.bits = Array.from({ length: this.generations }, () => new Uint8Array(this.bytesPerGen));
|
|
57
|
+
}
|
|
58
|
+
catch (err) {
|
|
59
|
+
this.disabled = true;
|
|
60
|
+
emitLogOnce("duplicate_filter_alloc_failed", "error", `duplicate filter allocation failed (${this.generations} × ${this.bytesPerGen} bytes); duplicate detection disabled for this process. Lower FRUGAL_DUPLICATE_CAPACITY / FRUGAL_DUPLICATE_GENERATIONS.`);
|
|
61
|
+
// Rethrow once so the caller records a single internal_errors point;
|
|
62
|
+
// every later call short-circuits on the latch above.
|
|
63
|
+
throw err;
|
|
64
|
+
}
|
|
65
|
+
this.genStartMs = nowMs;
|
|
66
|
+
}
|
|
67
|
+
this.rotate(nowMs);
|
|
68
|
+
const positions = this.positions(signature);
|
|
69
|
+
const bits = this.bits;
|
|
70
|
+
let present = true;
|
|
71
|
+
for (const pos of positions) {
|
|
72
|
+
const byte = pos >> 3;
|
|
73
|
+
const mask = 1 << (pos & 7);
|
|
74
|
+
let anySet = false;
|
|
75
|
+
for (let g = 0; g < this.generations; g++) {
|
|
76
|
+
if ((bits[g][byte] & mask) !== 0) {
|
|
77
|
+
anySet = true;
|
|
78
|
+
break;
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
if (!anySet) {
|
|
82
|
+
present = false;
|
|
83
|
+
break;
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
const active = bits[this.activeIndex];
|
|
87
|
+
for (const pos of positions) {
|
|
88
|
+
active[pos >> 3] |= 1 << (pos & 7);
|
|
89
|
+
}
|
|
90
|
+
if (++this.insertsSinceCheck >= SATURATION_CHECK_INTERVAL) {
|
|
91
|
+
this.insertsSinceCheck = 0;
|
|
92
|
+
this.maybeWarnSaturation();
|
|
93
|
+
}
|
|
94
|
+
return present ? DUP_REPEAT_SEEN : DUP_FIRST_SEEN;
|
|
95
|
+
}
|
|
96
|
+
clear() {
|
|
97
|
+
this.bits = null;
|
|
98
|
+
this.activeIndex = 0;
|
|
99
|
+
this.genStartMs = 0;
|
|
100
|
+
this.insertsSinceCheck = 0;
|
|
101
|
+
this.saturationWarned = false;
|
|
102
|
+
}
|
|
103
|
+
/**
|
|
104
|
+
* Warn (once per fill, re-armed by clear()) when the filter is saturating.
|
|
105
|
+
* Membership tests the UNION of all generations, so occupancy is measured on
|
|
106
|
+
* the union too — measuring only the active generation would let two
|
|
107
|
+
* generations at 49% (union ~74%) pass silently. Past ~50% union fill the
|
|
108
|
+
* effective false-positive rate is far above the configured target and the
|
|
109
|
+
* metric drifts toward all-repeat_seen.
|
|
110
|
+
*/
|
|
111
|
+
maybeWarnSaturation() {
|
|
112
|
+
if (this.saturationWarned || this.bits === null)
|
|
113
|
+
return;
|
|
114
|
+
const gens = this.bits;
|
|
115
|
+
let setBits = 0;
|
|
116
|
+
for (let byte = 0; byte < this.bytesPerGen; byte++) {
|
|
117
|
+
let union = 0;
|
|
118
|
+
for (let g = 0; g < gens.length; g++) {
|
|
119
|
+
union |= gens[g][byte];
|
|
120
|
+
}
|
|
121
|
+
setBits += POPCOUNT[union];
|
|
122
|
+
}
|
|
123
|
+
const fill = setBits / this.m;
|
|
124
|
+
if (fill >= SATURATION_FILL_THRESHOLD) {
|
|
125
|
+
this.saturationWarned = true;
|
|
126
|
+
emitLog("warn", `duplicate filter is ${Math.round(fill * 100)}% full — false-positive rate is degraded and repeat_seen counts are inflated. Raise FRUGAL_DUPLICATE_CAPACITY above the number of distinct requests per window.`);
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
rotate(nowMs) {
|
|
130
|
+
const elapsed = nowMs - this.genStartMs;
|
|
131
|
+
if (elapsed < this.rotateMs)
|
|
132
|
+
return;
|
|
133
|
+
const steps = Math.floor(elapsed / this.rotateMs);
|
|
134
|
+
const clears = Math.min(this.generations, steps);
|
|
135
|
+
for (let s = 0; s < clears; s++) {
|
|
136
|
+
this.activeIndex = (this.activeIndex + 1) % this.generations;
|
|
137
|
+
this.bits[this.activeIndex].fill(0);
|
|
138
|
+
}
|
|
139
|
+
this.genStartMs += steps * this.rotateMs;
|
|
140
|
+
}
|
|
141
|
+
positions(signature) {
|
|
142
|
+
const h1 = (parseInt(signature.slice(0, 8), 16) || 0) >>> 0;
|
|
143
|
+
const h2 = ((parseInt(signature.slice(8, 16), 16) || 0) | 1) >>> 0;
|
|
144
|
+
const out = new Array(this.k);
|
|
145
|
+
for (let i = 0; i < this.k; i++) {
|
|
146
|
+
out[i] = (h1 + i * h2) % this.m;
|
|
147
|
+
}
|
|
148
|
+
return out;
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
let shared = null;
|
|
152
|
+
export function getSharedDuplicateFilter(config) {
|
|
153
|
+
if (!config.duplicateDetection)
|
|
154
|
+
return null;
|
|
155
|
+
if (shared === null) {
|
|
156
|
+
shared = new RotatingBloomFilter({
|
|
157
|
+
capacity: config.duplicateCapacity,
|
|
158
|
+
fpRate: config.duplicateFpRate,
|
|
159
|
+
generations: config.duplicateGenerations,
|
|
160
|
+
windowSeconds: config.duplicateWindowSeconds,
|
|
161
|
+
});
|
|
162
|
+
}
|
|
163
|
+
return shared;
|
|
164
|
+
}
|
|
165
|
+
export function resetSharedDuplicateFilterForTests() {
|
|
166
|
+
shared = null;
|
|
167
|
+
}
|
|
168
|
+
//# sourceMappingURL=duplicate-filter.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"duplicate-filter.js","sourceRoot":"","sources":["../src/duplicate-filter.ts"],"names":[],"mappings":"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"}
|
package/dist/instrument.d.ts
CHANGED
|
@@ -12,6 +12,13 @@ export interface CallContext {
|
|
|
12
12
|
batched?: boolean;
|
|
13
13
|
/** Captured request kwargs for prompt analysis + cap utilization. */
|
|
14
14
|
requestPayload?: RequestPayload;
|
|
15
|
+
/**
|
|
16
|
+
* Optional caller-defined tag (e.g. a business unit) for the
|
|
17
|
+
* frugal.custom_label attribute. Set ONLY by
|
|
18
|
+
* the manual track API (trackAICall / startAICall) — provider wrappers must
|
|
19
|
+
* leave it unset. Values outside the config allowlist are coerced to "other".
|
|
20
|
+
*/
|
|
21
|
+
customLabel?: string;
|
|
15
22
|
}
|
|
16
23
|
export interface UsageReport {
|
|
17
24
|
responseModel?: string | null;
|
|
@@ -43,10 +50,35 @@ export declare class CallEmitter {
|
|
|
43
50
|
private finalized;
|
|
44
51
|
constructor(config: Config, ctx: CallContext, caller: CallerInfo, instruments: Instruments, startSec: number, provider: ProviderEntry, operation: string);
|
|
45
52
|
success(usage: UsageReport): void;
|
|
53
|
+
private recordCallShape;
|
|
54
|
+
private recordInternalError;
|
|
55
|
+
private recordDuplicate;
|
|
46
56
|
private recordCapUtilization;
|
|
47
57
|
private maybeAnalyze;
|
|
48
58
|
private recordStable;
|
|
49
59
|
error(err: unknown): void;
|
|
50
60
|
private baseAttrs;
|
|
51
61
|
}
|
|
62
|
+
export interface BatchRequestGroup {
|
|
63
|
+
requestModel: string;
|
|
64
|
+
count: number;
|
|
65
|
+
}
|
|
66
|
+
/**
|
|
67
|
+
* Record the calls a batch submission carries — one counter increment per
|
|
68
|
+
* (model, count) group so mixed-model batches attribute per model. Duration
|
|
69
|
+
* and error accounting for the submitting HTTP call stay on its CallEmitter;
|
|
70
|
+
* this only records the fanned-out request volume.
|
|
71
|
+
*/
|
|
72
|
+
export declare function recordBatchedCalls(config: Config, base: {
|
|
73
|
+
system: string;
|
|
74
|
+
operation: string;
|
|
75
|
+
}, caller: CallerInfo, groups: readonly BatchRequestGroup[]): void;
|
|
76
|
+
/**
|
|
77
|
+
* Record one batch result entry's token usage. No calls increment — the
|
|
78
|
+
* request was already counted at submission; results only attribute spend.
|
|
79
|
+
*/
|
|
80
|
+
export declare function recordBatchedUsage(config: Config, base: {
|
|
81
|
+
system: string;
|
|
82
|
+
operation: string;
|
|
83
|
+
}, caller: CallerInfo, requestModel: string, usage: UsageReport): void;
|
|
52
84
|
//# sourceMappingURL=instrument.d.ts.map
|
package/dist/instrument.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"instrument.d.ts","sourceRoot":"","sources":["../src/instrument.ts"],"names":[],"mappings":"AAUA,OAAO,EAGL,KAAK,UAAU,EAChB,MAAM,aAAa,CAAC;AACrB,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,aAAa,CAAC;AAE1C,OAAO,EAAkB,KAAK,WAAW,EAAE,MAAM,WAAW,CAAC;AAE7D,OAAO,
|
|
1
|
+
{"version":3,"file":"instrument.d.ts","sourceRoot":"","sources":["../src/instrument.ts"],"names":[],"mappings":"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"}
|