@frugalco/frugal-sdk-node 0.3.0 → 0.5.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 +73 -12
- package/dist/block-hash-lru.d.ts +8 -1
- package/dist/block-hash-lru.d.ts.map +1 -1
- package/dist/block-hash-lru.js +13 -4
- package/dist/block-hash-lru.js.map +1 -1
- package/dist/config.d.ts +33 -0
- package/dist/config.d.ts.map +1 -1
- package/dist/config.js +218 -27
- 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 +33 -0
- package/dist/instrument.d.ts.map +1 -1
- package/dist/instrument.js +277 -21
- package/dist/instrument.js.map +1 -1
- package/dist/model-cap.d.ts +24 -0
- package/dist/model-cap.d.ts.map +1 -0
- package/dist/model-cap.js +72 -0
- package/dist/model-cap.js.map +1 -0
- package/dist/otel.d.ts +32 -3
- package/dist/otel.d.ts.map +1 -1
- package/dist/otel.js +203 -7
- 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 +14 -0
- package/dist/semconv.d.ts.map +1 -1
- package/dist/semconv.js +38 -2
- 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 +4 -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
|
@@ -53,12 +53,20 @@ Set both or the wrapper silently returns the client unmodified:
|
|
|
53
53
|
| `FRUGAL_REPO_ROOT` | Absolute path to the source root; the strip prefix for `caller_file`. Defaults to the process working directory. |
|
|
54
54
|
| `FRUGAL_OTEL_EXPORTER_OTLP_ENDPOINT` | OTLP collector URL for Frugal metrics. Defaults to `https://metrics.frugal.co/opentelemetry/v1/metrics`. Falls back to `OTEL_EXPORTER_OTLP_ENDPOINT`. |
|
|
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
|
-
| `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. |
|
|
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. Only the **first 10** distinct entries are honored (a fixed cap, not configurable — each component multiplies every instrument's series count); the rest are dropped with one warning. |
|
|
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. |
|
|
62
|
+
| `FRUGAL_MAX_MODELS` | Cardinality cap — max distinct model strings (`gen_ai.request.model` and `gen_ai.response.model` share one registry) before further models collapse to `<overflow>`. Default `100`, which is ~50 logical models: a request alias (`gpt-4o`) and the dated snapshot the response reports (`gpt-4o-2024-08-06`) are two strings in the one shared registry. Set to `0` to disable. |
|
|
63
|
+
| `FRUGAL_MAX_SERIES_PER_INSTRUMENT` | OpenTelemetry's per-instrument limit on distinct label **combinations**. Default `50000` (OTel's own default is 2000, far too low for this SDK's label set); `0` means effectively unlimited (a 1,000,000 ceiling). |
|
|
61
64
|
| `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. |
|
|
65
|
+
| `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. |
|
|
66
|
+
| `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)). |
|
|
67
|
+
| `FRUGAL_DUPLICATE_CAPACITY` | Expected distinct requests per window (Bloom filter sizing). Default `10000`, max `1000000`. Raise if the saturation warning fires. |
|
|
68
|
+
| `FRUGAL_DUPLICATE_FP_RATE` | Target false-positive rate for the duplicate filter. Default `0.01`, max `0.5`. |
|
|
69
|
+
| `FRUGAL_DUPLICATE_GENERATIONS` | Rotating-Bloom generation count. Default `2`, max `16`. |
|
|
62
70
|
| `FRUGAL_DISABLED` | Set to `1` to disable all instrumentation without uninstalling. |
|
|
63
71
|
|
|
64
72
|
## What gets captured
|
|
@@ -68,32 +76,37 @@ Every wrapped call emits OTel metrics tagged with the caller's source file and f
|
|
|
68
76
|
| Metric | Kind | Description | Additional attributes |
|
|
69
77
|
| --------------------------------------- | --------- | ------------------------------------------------------------ | -------------------------------------- |
|
|
70
78
|
| `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 |
|
|
79
|
+
| `gen_ai.client.token.usage` | histogram | Input and output token counts | `gen_ai.token.type`, `frugal.history_bucket` (chat calls) |
|
|
80
|
+
| `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
81
|
| `frugal.gen_ai.cache_read_tokens` | histogram | Prompt cache hits | — |
|
|
74
82
|
| `frugal.gen_ai.cache_write_tokens` | histogram | Prompt cache writes | — |
|
|
75
83
|
| `frugal.gen_ai.reasoning_tokens` | histogram | OpenAI reasoning tokens (o-series), Anthropic thinking tokens| — |
|
|
84
|
+
| `frugal.gen_ai.agent.turns` | histogram | Model-response turns in a successful agent invocation | — |
|
|
76
85
|
| `frugal.gen_ai.errors` | counter | Errors by exception type | `frugal.error.type` |
|
|
77
|
-
| `frugal.gen_ai.internal_errors` | counter | frugal-metrics internal failures (cardinality
|
|
86
|
+
| `frugal.gen_ai.internal_errors` | counter | frugal-metrics internal failures (cardinality caps, bugs) | `frugal.internal_error.stage`, `frugal.instrument` (on `otel_cardinality_overflow` only) |
|
|
78
87
|
| `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` (
|
|
88
|
+
| `frugal.gen_ai.history_depth` | histogram | `messages.length` — every chat call, success or error (100%, not sampled) | — |
|
|
89
|
+
| `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
90
|
| `frugal.gen_ai.few_shot_count` | histogram | Few-shot exemplar pairs before final user (analyzer-sampled) | — |
|
|
91
|
+
| `frugal.gen_ai.few_shot_tokens` | histogram | Approx. token size of the few-shot example block (analyzer-sampled) | — |
|
|
81
92
|
| `frugal.gen_ai.noise_ratio` | histogram | HTML/base64/whitespace fraction of input (analyzer-sampled) | — |
|
|
82
93
|
| `frugal.gen_ai.cache_prefix_stable` | counter | Per-block prompt-cache stability (analyzer-sampled) | `frugal.block`, `frugal.stable` |
|
|
83
94
|
| `frugal.gen_ai.structured_output_hint` | counter | "Return JSON" prompts missing `response_format`/`tools` | — |
|
|
84
95
|
|
|
85
|
-
The
|
|
86
|
-
(`FRUGAL_PROMPT_ANALYSIS_SAMPLE_RATE`, default 10%)
|
|
87
|
-
|
|
96
|
+
The analyzer-sampled metrics fire on a fraction of calls
|
|
97
|
+
(`FRUGAL_PROMPT_ANALYSIS_SAMPLE_RATE`, default 10%). Everything else —
|
|
98
|
+
including `history_depth`, the `frugal.history_bucket` label on `calls` and
|
|
99
|
+
`token.usage`, the `frugal.structured_output_mode` label on `calls`, and
|
|
100
|
+
`duplicate_requests` — is 100%, never sampled.
|
|
88
101
|
|
|
89
102
|
Every metric carries this base attribute set:
|
|
90
103
|
|
|
91
104
|
- `frugal.project_id` — from your env vars (`frugal.customer_id` too when set); `frugal.component` is added on the per-component series when `FRUGAL_COMPONENTS` is set
|
|
92
105
|
- `frugal.caller_file`, `frugal.caller_function` — auto-detected from the call stack
|
|
93
106
|
- `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
|
-
- `gen_ai.request.model`, `gen_ai.response.model` — the model used
|
|
107
|
+
- `gen_ai.request.model`, `gen_ai.response.model` — the model used. Both draw on one shared registry capped by `FRUGAL_MAX_MODELS`; past the cap the value reads `<overflow>` (the same sentinel the caller labels use). `<unknown>` when the wrapper could not read a model off the request
|
|
95
108
|
- `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
|
|
109
|
+
- `frugal.batched` — `true` for batch-API paths (OpenAI Batch API submissions; Anthropic Message Batches submissions and results), `false` otherwise
|
|
97
110
|
|
|
98
111
|
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
112
|
|
|
@@ -161,9 +174,57 @@ try {
|
|
|
161
174
|
|
|
162
175
|
`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
176
|
|
|
164
|
-
|
|
177
|
+
For an agent invocation, set `operation: "invoke_agent"` and report the number
|
|
178
|
+
of model-response steps completed by the whole run:
|
|
165
179
|
|
|
166
|
-
|
|
180
|
+
```ts
|
|
181
|
+
const result = await trackAICall(
|
|
182
|
+
{ operation: "invoke_agent", model: "agent-model" },
|
|
183
|
+
async (report) => {
|
|
184
|
+
const run = await agent.invoke(task);
|
|
185
|
+
report({ agentTurns: run.turnCount });
|
|
186
|
+
return run.output;
|
|
187
|
+
},
|
|
188
|
+
);
|
|
189
|
+
```
|
|
190
|
+
|
|
191
|
+
The manual-handle form uses the same contract:
|
|
192
|
+
|
|
193
|
+
```ts
|
|
194
|
+
const handle = startAICall({ operation: "invoke_agent", model: "agent-model" });
|
|
195
|
+
try {
|
|
196
|
+
const run = await agent.invoke(task);
|
|
197
|
+
handle.end({ agentTurns: run.turnCount });
|
|
198
|
+
} catch (err) {
|
|
199
|
+
handle.fail(err);
|
|
200
|
+
throw err;
|
|
201
|
+
}
|
|
202
|
+
```
|
|
203
|
+
|
|
204
|
+
Provider wrappers continue to measure individual model calls. Agent frameworks
|
|
205
|
+
use `trackAICall` or `startAICall` to measure the enclosing invocation; dedicated
|
|
206
|
+
framework adapters are not available yet. A turn sample is emitted only for a
|
|
207
|
+
successful `invoke_agent` call with a positive-integer `agentTurns` value.
|
|
208
|
+
|
|
209
|
+
**Avoid double-counting tokens.** The examples above report `agentTurns` only,
|
|
210
|
+
which is correct when the agent's inner model-client calls are already wrapped
|
|
211
|
+
by a supported provider wrapper: those wrappers emit token usage per inner
|
|
212
|
+
call, so also reporting aggregate tokens on the `invoke_agent` call would
|
|
213
|
+
double-count them. Only when the inner calls are **not** wrapped, add the
|
|
214
|
+
aggregate `inputTokens` / `outputTokens` (and the other usage fields):
|
|
215
|
+
|
|
216
|
+
```ts
|
|
217
|
+
// Inner model calls are NOT wrapped by a provider wrapper.
|
|
218
|
+
report({
|
|
219
|
+
agentTurns: run.turnCount,
|
|
220
|
+
inputTokens: run.usage.inputTokens,
|
|
221
|
+
outputTokens: run.usage.outputTokens,
|
|
222
|
+
});
|
|
223
|
+
```
|
|
224
|
+
|
|
225
|
+
**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`.
|
|
226
|
+
|
|
227
|
+
**Usage** — `report(...)` / `end(...)` fields (all optional): `inputTokens`, `outputTokens`, `cacheReadTokens`, `cacheWriteTokens`, `reasoningTokens`, `responseModel`, `agentTurns`.
|
|
167
228
|
|
|
168
229
|
The same env-var configuration, safety guarantees, and exported metrics apply.
|
|
169
230
|
|
package/dist/block-hash-lru.d.ts
CHANGED
|
@@ -5,8 +5,15 @@ export interface BlockHashes {
|
|
|
5
5
|
schemaHash: string | null;
|
|
6
6
|
}
|
|
7
7
|
export declare class BlockHashLRU {
|
|
8
|
-
private readonly capacity;
|
|
9
8
|
private readonly map;
|
|
9
|
+
private readonly capacity;
|
|
10
|
+
/**
|
|
11
|
+
* A capacity of 0 or less means "use the default", never "never evict". The
|
|
12
|
+
* capacity is threaded from FRUGAL_MAX_CALL_SITES, where 0 legitimately means
|
|
13
|
+
* "don't cap the call-site LABEL" — but this map is process memory, not label
|
|
14
|
+
* cardinality, and an unbounded one grows with every distinct call site for
|
|
15
|
+
* the life of the process.
|
|
16
|
+
*/
|
|
10
17
|
constructor(capacity?: number);
|
|
11
18
|
get(callSiteId: string): BlockHashes | undefined;
|
|
12
19
|
set(callSiteId: string, hashes: BlockHashes): void;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"block-hash-lru.d.ts","sourceRoot":"","sources":["../src/block-hash-lru.ts"],"names":[],"mappings":"AAOA,MAAM,WAAW,WAAW;IAC1B,UAAU,EAAE,MAAM,GAAG,IAAI,CAAC;IAC1B,QAAQ,EAAE,MAAM,GAAG,IAAI,CAAC;IACxB,SAAS,EAAE,MAAM,GAAG,IAAI,CAAC;IACzB,UAAU,EAAE,MAAM,GAAG,IAAI,CAAC;CAC3B;
|
|
1
|
+
{"version":3,"file":"block-hash-lru.d.ts","sourceRoot":"","sources":["../src/block-hash-lru.ts"],"names":[],"mappings":"AAOA,MAAM,WAAW,WAAW;IAC1B,UAAU,EAAE,MAAM,GAAG,IAAI,CAAC;IAC1B,QAAQ,EAAE,MAAM,GAAG,IAAI,CAAC;IACxB,SAAS,EAAE,MAAM,GAAG,IAAI,CAAC;IACzB,UAAU,EAAE,MAAM,GAAG,IAAI,CAAC;CAC3B;AAKD,qBAAa,YAAY;IACvB,OAAO,CAAC,QAAQ,CAAC,GAAG,CAAkC;IACtD,OAAO,CAAC,QAAQ,CAAC,QAAQ,CAAS;IAElC;;;;;;OAMG;gBACS,QAAQ,GAAE,MAAyB;IAI/C,GAAG,CAAC,UAAU,EAAE,MAAM,GAAG,WAAW,GAAG,SAAS;IAQhD,GAAG,CAAC,UAAU,EAAE,MAAM,EAAE,MAAM,EAAE,WAAW,GAAG,IAAI;IAUlD,IAAI,IAAI,MAAM;IAId,KAAK,IAAI,IAAI;CAGd;AAID,wBAAgB,qBAAqB,CAAC,QAAQ,CAAC,EAAE,MAAM,GAAG,YAAY,CAGrE;AAED,wBAAgB,+BAA+B,IAAI,IAAI,CAEtD"}
|
package/dist/block-hash-lru.js
CHANGED
|
@@ -4,11 +4,20 @@
|
|
|
4
4
|
//
|
|
5
5
|
// Default capacity matches the existing call-site cardinality cap so a busy
|
|
6
6
|
// process can't blow memory by tracking thousands of distinct sites.
|
|
7
|
+
/** Capacity used whenever the caller supplies nothing usable. */
|
|
8
|
+
const DEFAULT_CAPACITY = 500;
|
|
7
9
|
export class BlockHashLRU {
|
|
8
|
-
capacity;
|
|
9
10
|
map = new Map();
|
|
10
|
-
|
|
11
|
-
|
|
11
|
+
capacity;
|
|
12
|
+
/**
|
|
13
|
+
* A capacity of 0 or less means "use the default", never "never evict". The
|
|
14
|
+
* capacity is threaded from FRUGAL_MAX_CALL_SITES, where 0 legitimately means
|
|
15
|
+
* "don't cap the call-site LABEL" — but this map is process memory, not label
|
|
16
|
+
* cardinality, and an unbounded one grows with every distinct call site for
|
|
17
|
+
* the life of the process.
|
|
18
|
+
*/
|
|
19
|
+
constructor(capacity = DEFAULT_CAPACITY) {
|
|
20
|
+
this.capacity = capacity > 0 ? capacity : DEFAULT_CAPACITY;
|
|
12
21
|
}
|
|
13
22
|
get(callSiteId) {
|
|
14
23
|
const entry = this.map.get(callSiteId);
|
|
@@ -22,7 +31,7 @@ export class BlockHashLRU {
|
|
|
22
31
|
if (this.map.has(callSiteId)) {
|
|
23
32
|
this.map.delete(callSiteId);
|
|
24
33
|
}
|
|
25
|
-
else if (this.
|
|
34
|
+
else if (this.map.size >= this.capacity) {
|
|
26
35
|
const oldest = this.map.keys().next().value;
|
|
27
36
|
if (oldest !== undefined)
|
|
28
37
|
this.map.delete(oldest);
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"block-hash-lru.js","sourceRoot":"","sources":["../src/block-hash-lru.ts"],"names":[],"mappings":"AAAA,0EAA0E;AAC1E,4EAA4E;AAC5E,uCAAuC;AACvC,EAAE;AACF,4EAA4E;AAC5E,qEAAqE;AASrE,MAAM,OAAO,YAAY;
|
|
1
|
+
{"version":3,"file":"block-hash-lru.js","sourceRoot":"","sources":["../src/block-hash-lru.ts"],"names":[],"mappings":"AAAA,0EAA0E;AAC1E,4EAA4E;AAC5E,uCAAuC;AACvC,EAAE;AACF,4EAA4E;AAC5E,qEAAqE;AASrE,iEAAiE;AACjE,MAAM,gBAAgB,GAAG,GAAG,CAAC;AAE7B,MAAM,OAAO,YAAY;IACN,GAAG,GAAG,IAAI,GAAG,EAAuB,CAAC;IACrC,QAAQ,CAAS;IAElC;;;;;;OAMG;IACH,YAAY,WAAmB,gBAAgB;QAC7C,IAAI,CAAC,QAAQ,GAAG,QAAQ,GAAG,CAAC,CAAC,CAAC,CAAC,QAAQ,CAAC,CAAC,CAAC,gBAAgB,CAAC;IAC7D,CAAC;IAED,GAAG,CAAC,UAAkB;QACpB,MAAM,KAAK,GAAG,IAAI,CAAC,GAAG,CAAC,GAAG,CAAC,UAAU,CAAC,CAAC;QACvC,IAAI,KAAK,KAAK,SAAS;YAAE,OAAO,SAAS,CAAC;QAC1C,IAAI,CAAC,GAAG,CAAC,MAAM,CAAC,UAAU,CAAC,CAAC;QAC5B,IAAI,CAAC,GAAG,CAAC,GAAG,CAAC,UAAU,EAAE,KAAK,CAAC,CAAC;QAChC,OAAO,KAAK,CAAC;IACf,CAAC;IAED,GAAG,CAAC,UAAkB,EAAE,MAAmB;QACzC,IAAI,IAAI,CAAC,GAAG,CAAC,GAAG,CAAC,UAAU,CAAC,EAAE,CAAC;YAC7B,IAAI,CAAC,GAAG,CAAC,MAAM,CAAC,UAAU,CAAC,CAAC;QAC9B,CAAC;aAAM,IAAI,IAAI,CAAC,GAAG,CAAC,IAAI,IAAI,IAAI,CAAC,QAAQ,EAAE,CAAC;YAC1C,MAAM,MAAM,GAAG,IAAI,CAAC,GAAG,CAAC,IAAI,EAAE,CAAC,IAAI,EAAE,CAAC,KAAK,CAAC;YAC5C,IAAI,MAAM,KAAK,SAAS;gBAAE,IAAI,CAAC,GAAG,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC;QACpD,CAAC;QACD,IAAI,CAAC,GAAG,CAAC,GAAG,CAAC,UAAU,EAAE,MAAM,CAAC,CAAC;IACnC,CAAC;IAED,IAAI;QACF,OAAO,IAAI,CAAC,GAAG,CAAC,IAAI,CAAC;IACvB,CAAC;IAED,KAAK;QACH,IAAI,CAAC,GAAG,CAAC,KAAK,EAAE,CAAC;IACnB,CAAC;CACF;AAED,IAAI,MAAM,GAAwB,IAAI,CAAC;AAEvC,MAAM,UAAU,qBAAqB,CAAC,QAAiB;IACrD,IAAI,MAAM,KAAK,IAAI;QAAE,MAAM,GAAG,IAAI,YAAY,CAAC,QAAQ,CAAC,CAAC;IACzD,OAAO,MAAM,CAAC;AAChB,CAAC;AAED,MAAM,UAAU,+BAA+B;IAC7C,MAAM,GAAG,IAAI,CAAC;AAChB,CAAC"}
|
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;
|
|
@@ -18,9 +24,36 @@ export interface Config {
|
|
|
18
24
|
exportIntervalMs: number;
|
|
19
25
|
/** Max distinct (file, function) combinations to attribute before overflow. 0 = unlimited. */
|
|
20
26
|
maxCallSites: number;
|
|
27
|
+
/**
|
|
28
|
+
* OTel's per-instrument limit on distinct attribute SETS. Beyond it the SDK
|
|
29
|
+
* merges everything into one `{otel.metric.overflow: true}` series with every
|
|
30
|
+
* other attribute dropped. 0 = disabled (effectively unlimited).
|
|
31
|
+
*/
|
|
32
|
+
maxSeriesPerInstrument: number;
|
|
33
|
+
/**
|
|
34
|
+
* Max distinct model strings (request + response share one registry) to
|
|
35
|
+
* attribute before they collapse to the `<overflow>` sentinel. 0 = unlimited.
|
|
36
|
+
*/
|
|
37
|
+
maxModels: number;
|
|
21
38
|
/** Fraction of analyzed calls to emit prompt-analysis metrics for, 0..1. */
|
|
22
39
|
promptAnalysisSampleRate: number;
|
|
40
|
+
duplicateDetection: boolean;
|
|
41
|
+
duplicateWindowSeconds: number;
|
|
42
|
+
duplicateCapacity: number;
|
|
43
|
+
duplicateFpRate: number;
|
|
44
|
+
duplicateGenerations: number;
|
|
23
45
|
}
|
|
46
|
+
/**
|
|
47
|
+
* Load the configuration, or null when the SDK should stay a no-op.
|
|
48
|
+
*
|
|
49
|
+
* Every wrapX() calls this at the customer's module scope, so a throw here
|
|
50
|
+
* crashes the host app before it has run a line of its own code. Nothing in
|
|
51
|
+
* the body is supposed to throw — but it reads the environment and touches the
|
|
52
|
+
* filesystem (process.cwd() raises ENOENT when the working directory has been
|
|
53
|
+
* removed, realpathSync can raise on a broken mount), so the whole thing is
|
|
54
|
+
* contained: any failure disables the SDK with one diagnostic instead of
|
|
55
|
+
* taking the process with it.
|
|
56
|
+
*/
|
|
24
57
|
export declare function loadConfig(): Config | null;
|
|
25
58
|
/** Test-only hook to re-arm the one-shot warning. */
|
|
26
59
|
export declare function resetWarningStateForTests(): void;
|
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;;;;OAIG;IACH,sBAAsB,EAAE,MAAM,CAAC;IAC/B;;;OAGG;IACH,SAAS,EAAE,MAAM,CAAC;IAClB,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;AAoMD;;;;;;;;;;GAUG;AACH,wBAAgB,UAAU,IAAI,MAAM,GAAG,IAAI,CAW1C;AAsQD,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",
|
|
@@ -49,13 +51,62 @@ const ENV = {
|
|
|
49
51
|
OTLP_HEADERS: "OTEL_EXPORTER_OTLP_HEADERS",
|
|
50
52
|
EXPORT_INTERVAL_MS: "FRUGAL_METRIC_EXPORT_INTERVAL_MS",
|
|
51
53
|
MAX_CALL_SITES: "FRUGAL_MAX_CALL_SITES",
|
|
54
|
+
MAX_SERIES_PER_INSTRUMENT: "FRUGAL_MAX_SERIES_PER_INSTRUMENT",
|
|
55
|
+
MAX_MODELS: "FRUGAL_MAX_MODELS",
|
|
52
56
|
PROMPT_ANALYSIS_SAMPLE_RATE: "FRUGAL_PROMPT_ANALYSIS_SAMPLE_RATE",
|
|
57
|
+
DUPLICATE_DETECTION: "FRUGAL_DUPLICATE_REQUEST_DETECTION",
|
|
58
|
+
DUPLICATE_WINDOW_SECONDS: "FRUGAL_DUPLICATE_WINDOW_SECONDS",
|
|
59
|
+
DUPLICATE_CAPACITY: "FRUGAL_DUPLICATE_CAPACITY",
|
|
60
|
+
DUPLICATE_FP_RATE: "FRUGAL_DUPLICATE_FP_RATE",
|
|
61
|
+
DUPLICATE_GENERATIONS: "FRUGAL_DUPLICATE_GENERATIONS",
|
|
53
62
|
};
|
|
54
63
|
const DEFAULT_OTLP_ENDPOINT = "https://metrics.frugal.co/opentelemetry/v1/metrics";
|
|
55
64
|
const DEFAULT_MAX_CALL_SITES = 500;
|
|
65
|
+
// OTel's own default per-instrument limit is 2000 attribute SETS, which 500
|
|
66
|
+
// call sites × models × token types blows through immediately. See
|
|
67
|
+
// docs/quickstart.md §Cardinality caps for the memory reasoning behind 50000.
|
|
68
|
+
const DEFAULT_MAX_SERIES_PER_INSTRUMENT = 50_000;
|
|
69
|
+
// 100, not 50: one logical model routinely burns TWO registry slots, because
|
|
70
|
+
// the request carries an alias ("gpt-4o", "claude-sonnet-4-6") while the
|
|
71
|
+
// response reports the dated snapshot it resolved to ("gpt-4o-2024-08-06",
|
|
72
|
+
// "claude-sonnet-4-6-20260115"), and both labels share one registry. So a
|
|
73
|
+
// deployment using 50 logical models needs ~100 strings.
|
|
74
|
+
const DEFAULT_MAX_MODELS = 100;
|
|
75
|
+
const DEFAULT_EXPORT_INTERVAL_MS = 60_000;
|
|
56
76
|
const DEFAULT_PROMPT_ANALYSIS_SAMPLE_RATE = 0.1;
|
|
77
|
+
const DEFAULT_DUPLICATE_WINDOW_SECONDS = 3600;
|
|
78
|
+
const DEFAULT_DUPLICATE_CAPACITY = 10_000;
|
|
79
|
+
const DEFAULT_DUPLICATE_FP_RATE = 0.01;
|
|
80
|
+
const DEFAULT_DUPLICATE_GENERATIONS = 2;
|
|
81
|
+
// Bloom memory is ~(1.44·log2(1/p))/8 bytes × capacity × generations — the fp
|
|
82
|
+
// rate matters: ~1.2 B/entry at the 0.01 default (≈24 KB total at defaults)
|
|
83
|
+
// but ~3.6 B/entry at the 1e-6 floor the filter clamps to. The caps below
|
|
84
|
+
// bound the worst case to ≈58 MB (capacity 1e6 × 16 generations × 3.6 B),
|
|
85
|
+
// allocated inside the customer's first call — large, but a knowable ceiling
|
|
86
|
+
// rather than a fat-fingered env var demanding gigabytes. Saturation checks
|
|
87
|
+
// popcount the full allocation every 1024th insert, so the ceiling also bounds
|
|
88
|
+
// that hot-path cost.
|
|
89
|
+
const MAX_DUPLICATE_CAPACITY = 1_000_000;
|
|
90
|
+
const MAX_DUPLICATE_GENERATIONS = 16;
|
|
91
|
+
// Above the documented window there is no additional duplicate signal — only a
|
|
92
|
+
// filter that effectively never rotates and saturates for the process
|
|
93
|
+
// lifetime. One day is already far beyond any retry/cache horizon.
|
|
94
|
+
const MAX_DUPLICATE_WINDOW_SECONDS = 86_400;
|
|
95
|
+
// bloomParams clamps fp to at most 0.5 (the mathematical ceiling where the
|
|
96
|
+
// filter degenerates); values in [0.5, 1) must warn HERE, not silently differ
|
|
97
|
+
// from what the filter actually uses.
|
|
98
|
+
const MAX_DUPLICATE_FP_RATE = 0.5;
|
|
57
99
|
/** Upper bound on customer-registered provider names — keeps label cardinality bounded. */
|
|
58
100
|
const MAX_CUSTOM_PROVIDERS = 10;
|
|
101
|
+
// Hard bound on the FRUGAL_COMPONENTS fan-out list. Every component multiplies
|
|
102
|
+
// each instrument's series count by one, so like MAX_CUSTOM_LABELS this is a
|
|
103
|
+
// module-private literal with no env override: the cap is the cardinality
|
|
104
|
+
// contract for the frugal.component label, not a tuning knob.
|
|
105
|
+
const MAX_COMPONENTS = 10;
|
|
106
|
+
// Hard bound on the frugal.custom_label allowlist. Deliberately a
|
|
107
|
+
// module-private literal with no env override: the cap is the cardinality
|
|
108
|
+
// contract for the label, so it must not be user-tunable.
|
|
109
|
+
const MAX_CUSTOM_LABELS = 20;
|
|
59
110
|
let warnedOnce = false;
|
|
60
111
|
function truthy(value) {
|
|
61
112
|
if (!value)
|
|
@@ -63,13 +114,101 @@ function truthy(value) {
|
|
|
63
114
|
const v = value.trim().toLowerCase();
|
|
64
115
|
return v === "1" || v === "true" || v === "yes" || v === "on";
|
|
65
116
|
}
|
|
117
|
+
function explicitlyOff(value) {
|
|
118
|
+
if (value === undefined)
|
|
119
|
+
return false;
|
|
120
|
+
const v = value.trim().toLowerCase();
|
|
121
|
+
return v === "0" || v === "false" || v === "no" || v === "off";
|
|
122
|
+
}
|
|
123
|
+
// Number(raw) validates the FULL string (unlike parseInt/parseFloat, whose
|
|
124
|
+
// prefix parsing turns "1e6" into 1 and "10,000" into 10 — a capacity of 1
|
|
125
|
+
// means a 10-bit filter that saturates instantly). Warnings are de-duped per
|
|
126
|
+
// var: loadConfig() runs on every call, so a bad value must not flood the
|
|
127
|
+
// bounded debug ring buffer.
|
|
128
|
+
function parsePositive(raw, name, fallback, { integer = false, max } = {}) {
|
|
129
|
+
if (!raw || !raw.trim())
|
|
130
|
+
return fallback;
|
|
131
|
+
const parsed = Number(raw.trim());
|
|
132
|
+
const valid = Number.isFinite(parsed) && parsed > 0 && (!integer || Number.isInteger(parsed));
|
|
133
|
+
if (!valid) {
|
|
134
|
+
emitLogOnce(`bad_env_${name}`, "warn", `could not parse ${name}=${raw}; using default ${fallback}`);
|
|
135
|
+
return fallback;
|
|
136
|
+
}
|
|
137
|
+
if (max !== undefined && parsed > max) {
|
|
138
|
+
emitLogOnce(`clamped_env_${name}`, "warn", `${name}=${raw} exceeds the maximum ${max}; clamping to ${max}`);
|
|
139
|
+
return max;
|
|
140
|
+
}
|
|
141
|
+
return parsed;
|
|
142
|
+
}
|
|
143
|
+
// Shared shape for the integer knobs: the cardinality caps (non-negative, 0 =
|
|
144
|
+
// "disabled") and the export interval (strictly positive).
|
|
145
|
+
//
|
|
146
|
+
// Digits only, deliberately: Number.parseInt PREFIX-parses, so "1e6" becomes 1,
|
|
147
|
+
// "50_000" becomes 50 and "12abc" becomes 12 — each silently installing a cap
|
|
148
|
+
// orders of magnitude tighter than the operator asked for, which then collapses
|
|
149
|
+
// real labels into <overflow> with no diagnostic. A value that is not entirely
|
|
150
|
+
// digits (after trim) is treated as a typo: warn once, use the default. There is
|
|
151
|
+
// deliberately NO upper bound — a gateway in front of the metrics backend does
|
|
152
|
+
// the aggregating, so a large per-process cap is the operator's call.
|
|
153
|
+
const DIGITS_ONLY = /^\d+$/;
|
|
154
|
+
/**
|
|
155
|
+
* Parse a digits-only integer of at least `minimum`, warning once and falling
|
|
156
|
+
* back to `fallback` on anything else.
|
|
157
|
+
*
|
|
158
|
+
* The safe-integer check is a VALIDITY test, not a clamp: past
|
|
159
|
+
* Number.MAX_SAFE_INTEGER integer arithmetic silently loses precision and a
|
|
160
|
+
* long enough digit string parses to Infinity, so such a value is not a cap
|
|
161
|
+
* anyone can reason about. It is rejected exactly like "12abc" — default plus
|
|
162
|
+
* one warning — and every representable value, however large, is honored
|
|
163
|
+
* verbatim.
|
|
164
|
+
*/
|
|
165
|
+
function parseDigitsInt(raw, name, fallback, minimum) {
|
|
166
|
+
if (!raw || !raw.trim())
|
|
167
|
+
return fallback;
|
|
168
|
+
const trimmed = raw.trim();
|
|
169
|
+
if (DIGITS_ONLY.test(trimmed)) {
|
|
170
|
+
const parsed = Number(trimmed);
|
|
171
|
+
if (Number.isSafeInteger(parsed) && parsed >= minimum)
|
|
172
|
+
return parsed;
|
|
173
|
+
}
|
|
174
|
+
emitLogOnce(`bad_env_${name}`, "warn", `could not parse ${name}=${raw}; using default ${fallback}`);
|
|
175
|
+
return fallback;
|
|
176
|
+
}
|
|
177
|
+
/** Cardinality caps: 0 means "disabled". */
|
|
178
|
+
function parseNonNegativeInt(raw, name, fallback) {
|
|
179
|
+
return parseDigitsInt(raw, name, fallback, 0);
|
|
180
|
+
}
|
|
181
|
+
/** Knobs where 0 is meaningless (or fatal downstream) rather than "off". */
|
|
182
|
+
function parsePositiveInt(raw, name, fallback) {
|
|
183
|
+
return parseDigitsInt(raw, name, fallback, 1);
|
|
184
|
+
}
|
|
66
185
|
function warnOnce(message) {
|
|
67
186
|
if (warnedOnce)
|
|
68
187
|
return;
|
|
69
188
|
warnedOnce = true;
|
|
70
189
|
emitLog("warn", `disabled: ${message}`);
|
|
71
190
|
}
|
|
191
|
+
/**
|
|
192
|
+
* Load the configuration, or null when the SDK should stay a no-op.
|
|
193
|
+
*
|
|
194
|
+
* Every wrapX() calls this at the customer's module scope, so a throw here
|
|
195
|
+
* crashes the host app before it has run a line of its own code. Nothing in
|
|
196
|
+
* the body is supposed to throw — but it reads the environment and touches the
|
|
197
|
+
* filesystem (process.cwd() raises ENOENT when the working directory has been
|
|
198
|
+
* removed, realpathSync can raise on a broken mount), so the whole thing is
|
|
199
|
+
* contained: any failure disables the SDK with one diagnostic instead of
|
|
200
|
+
* taking the process with it.
|
|
201
|
+
*/
|
|
72
202
|
export function loadConfig() {
|
|
203
|
+
try {
|
|
204
|
+
return loadConfigUnguarded();
|
|
205
|
+
}
|
|
206
|
+
catch (err) {
|
|
207
|
+
emitLogOnce("config_load_failed", "error", `configuration could not be loaded (${err instanceof Error ? err.message : String(err)}); frugal-metrics is disabled for this process`);
|
|
208
|
+
return null;
|
|
209
|
+
}
|
|
210
|
+
}
|
|
211
|
+
function loadConfigUnguarded() {
|
|
73
212
|
if (truthy(process.env[ENV.DISABLED]))
|
|
74
213
|
return null;
|
|
75
214
|
const customerId = process.env[ENV.CUSTOMER_ID] || null;
|
|
@@ -118,11 +257,25 @@ export function loadConfig() {
|
|
|
118
257
|
.filter((e) => e.length > 0);
|
|
119
258
|
// Optional comma-separated logical components (e.g. "ai-handler,reco"). Each
|
|
120
259
|
// becomes an extra per-component series on top of the base project series.
|
|
260
|
+
// Each component multiplies every instrument's series count by one, so the
|
|
261
|
+
// list is truncated to MAX_COMPONENTS at parse time. No sentinel: a dropped
|
|
262
|
+
// component simply produces no per-component series — the base project-level
|
|
263
|
+
// series still counts every call.
|
|
264
|
+
//
|
|
265
|
+
// Deduped (order-preservingly, via Set insertion order) BEFORE the slice: a
|
|
266
|
+
// repeated entry costs no extra series, so letting duplicates consume budget
|
|
267
|
+
// would drop real components — and warn about it — for no cardinality gain.
|
|
121
268
|
const componentsRaw = process.env[ENV.COMPONENTS] ?? "";
|
|
122
|
-
const
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
269
|
+
const componentsAll = [
|
|
270
|
+
...new Set(componentsRaw
|
|
271
|
+
.split(",")
|
|
272
|
+
.map((e) => e.trim())
|
|
273
|
+
.filter((e) => e.length > 0)),
|
|
274
|
+
];
|
|
275
|
+
const components = componentsAll.slice(0, MAX_COMPONENTS);
|
|
276
|
+
if (components.length < componentsAll.length) {
|
|
277
|
+
emitLogOnce("components_truncated", "warn", `${ENV.COMPONENTS} lists ${componentsAll.length} entries; only the first ${MAX_COMPONENTS} are honored and ${componentsAll.length - components.length} are dropped (no per-component series for them).`);
|
|
278
|
+
}
|
|
126
279
|
// Optional comma-separated allowlist of custom provider names that pass through
|
|
127
280
|
// verbatim (e.g. "together_ai,fireworks"). Deduped and capped at
|
|
128
281
|
// MAX_CUSTOM_PROVIDERS so a misconfigured value can't blow up label cardinality.
|
|
@@ -137,31 +290,41 @@ export function loadConfig() {
|
|
|
137
290
|
if (customProvidersAll.length > MAX_CUSTOM_PROVIDERS) {
|
|
138
291
|
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
292
|
}
|
|
293
|
+
// Optional comma-separated allowlist for the frugal.custom_label attribute on
|
|
294
|
+
// manual track calls. Only the first MAX_CUSTOM_LABELS comma-separated
|
|
295
|
+
// entries are honored (sliced BEFORE trim/dedupe — the position in the raw
|
|
296
|
+
// list is the contract); customLabel values outside the allowlist are
|
|
297
|
+
// coerced to "other" at emit time.
|
|
298
|
+
const customLabelsRaw = process.env[ENV.CUSTOM_LABEL] ?? "";
|
|
299
|
+
const customLabelEntries = customLabelsRaw.split(",");
|
|
300
|
+
const customLabelsParsed = [
|
|
301
|
+
...new Set(customLabelEntries
|
|
302
|
+
.slice(0, MAX_CUSTOM_LABELS)
|
|
303
|
+
.map((e) => e.trim())
|
|
304
|
+
.filter((e) => e.length > 0)),
|
|
305
|
+
];
|
|
306
|
+
// "other" is the coercion sentinel, never a legitimate allowlist entry: a
|
|
307
|
+
// real unit named "other" would inseparably absorb every non-allowlisted
|
|
308
|
+
// value from unrelated call sites.
|
|
309
|
+
const customLabels = customLabelsParsed.filter((e) => e !== CUSTOM_LABEL_OTHER);
|
|
310
|
+
if (customLabels.length < customLabelsParsed.length) {
|
|
311
|
+
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.`);
|
|
312
|
+
}
|
|
313
|
+
if (customLabelEntries.length > MAX_CUSTOM_LABELS && customLabelsRaw.trim()) {
|
|
314
|
+
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}".`);
|
|
315
|
+
}
|
|
140
316
|
const otlpEndpoint = process.env[ENV.FRUGAL_OTLP_ENDPOINT] ??
|
|
141
317
|
process.env[ENV.OTLP_ENDPOINT] ??
|
|
142
318
|
DEFAULT_OTLP_ENDPOINT;
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
}
|
|
153
|
-
}
|
|
154
|
-
const maxCallSitesRaw = process.env[ENV.MAX_CALL_SITES];
|
|
155
|
-
let maxCallSites = DEFAULT_MAX_CALL_SITES;
|
|
156
|
-
if (maxCallSitesRaw && maxCallSitesRaw.trim()) {
|
|
157
|
-
const parsed = Number.parseInt(maxCallSitesRaw, 10);
|
|
158
|
-
if (Number.isFinite(parsed) && parsed >= 0) {
|
|
159
|
-
maxCallSites = parsed;
|
|
160
|
-
}
|
|
161
|
-
else {
|
|
162
|
-
emitLog("warn", `could not parse ${ENV.MAX_CALL_SITES}=${maxCallSitesRaw}; using default ${maxCallSites}`);
|
|
163
|
-
}
|
|
164
|
-
}
|
|
319
|
+
// Strictly positive: PeriodicExportingMetricReader THROWS on an interval of
|
|
320
|
+
// 0 or less, and that throw propagates out of getInstruments on the
|
|
321
|
+
// customer's first call. Every wrapper catches it, so the SDK degrades to a
|
|
322
|
+
// no-op rather than breaking the host — but a typo silently costing all
|
|
323
|
+
// metrics is worse than a typo that warns and uses the default.
|
|
324
|
+
const exportIntervalMs = parsePositiveInt(process.env[ENV.EXPORT_INTERVAL_MS], ENV.EXPORT_INTERVAL_MS, DEFAULT_EXPORT_INTERVAL_MS);
|
|
325
|
+
const maxCallSites = parseNonNegativeInt(process.env[ENV.MAX_CALL_SITES], ENV.MAX_CALL_SITES, DEFAULT_MAX_CALL_SITES);
|
|
326
|
+
const maxSeriesPerInstrument = parseNonNegativeInt(process.env[ENV.MAX_SERIES_PER_INSTRUMENT], ENV.MAX_SERIES_PER_INSTRUMENT, DEFAULT_MAX_SERIES_PER_INSTRUMENT);
|
|
327
|
+
const maxModels = parseNonNegativeInt(process.env[ENV.MAX_MODELS], ENV.MAX_MODELS, DEFAULT_MAX_MODELS);
|
|
165
328
|
const sampleRateRaw = process.env[ENV.PROMPT_ANALYSIS_SAMPLE_RATE];
|
|
166
329
|
let promptAnalysisSampleRate = DEFAULT_PROMPT_ANALYSIS_SAMPLE_RATE;
|
|
167
330
|
if (sampleRateRaw && sampleRateRaw.trim()) {
|
|
@@ -170,21 +333,49 @@ export function loadConfig() {
|
|
|
170
333
|
promptAnalysisSampleRate = parsed;
|
|
171
334
|
}
|
|
172
335
|
else {
|
|
173
|
-
|
|
336
|
+
emitLogOnce(`bad_env_${ENV.PROMPT_ANALYSIS_SAMPLE_RATE}`, "warn", `could not parse ${ENV.PROMPT_ANALYSIS_SAMPLE_RATE}=${sampleRateRaw}; using default ${promptAnalysisSampleRate}`);
|
|
174
337
|
}
|
|
175
338
|
}
|
|
339
|
+
const fpRate = parsePositive(process.env[ENV.DUPLICATE_FP_RATE], ENV.DUPLICATE_FP_RATE, DEFAULT_DUPLICATE_FP_RATE);
|
|
340
|
+
let duplicateFpRate = fpRate;
|
|
341
|
+
if (fpRate >= 1) {
|
|
342
|
+
emitLogOnce(`bad_range_${ENV.DUPLICATE_FP_RATE}`, "warn", `${ENV.DUPLICATE_FP_RATE}=${fpRate} must be in (0, 1); using default ${DEFAULT_DUPLICATE_FP_RATE}`);
|
|
343
|
+
duplicateFpRate = DEFAULT_DUPLICATE_FP_RATE;
|
|
344
|
+
}
|
|
345
|
+
else if (fpRate > MAX_DUPLICATE_FP_RATE) {
|
|
346
|
+
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}`);
|
|
347
|
+
duplicateFpRate = MAX_DUPLICATE_FP_RATE;
|
|
348
|
+
}
|
|
349
|
+
// The kill switch disables only on an explicit off value. Operators reach
|
|
350
|
+
// for this knob on privacy/overhead grounds, so a value we don't recognize
|
|
351
|
+
// ("disabled", "n", …) must not silently leave full-payload hashing on.
|
|
352
|
+
const dupDetectionRaw = process.env[ENV.DUPLICATE_DETECTION];
|
|
353
|
+
if (dupDetectionRaw !== undefined &&
|
|
354
|
+
dupDetectionRaw.trim() !== "" &&
|
|
355
|
+
!explicitlyOff(dupDetectionRaw) &&
|
|
356
|
+
!truthy(dupDetectionRaw)) {
|
|
357
|
+
emitLogOnce(`bad_env_${ENV.DUPLICATE_DETECTION}`, "warn", `unrecognized ${ENV.DUPLICATE_DETECTION}=${dupDetectionRaw}; duplicate detection remains ENABLED. Use 0/false/no/off to disable.`);
|
|
358
|
+
}
|
|
176
359
|
return {
|
|
177
360
|
customerId,
|
|
178
361
|
projectId: projectId,
|
|
179
362
|
components,
|
|
180
363
|
customProviders,
|
|
364
|
+
customLabels,
|
|
181
365
|
repoRoot: repoRootAbs,
|
|
182
366
|
pathBlockList,
|
|
183
367
|
otlpEndpoint,
|
|
184
368
|
otlpHeaders: otlpHeaders,
|
|
185
369
|
exportIntervalMs,
|
|
186
370
|
maxCallSites,
|
|
371
|
+
maxSeriesPerInstrument,
|
|
372
|
+
maxModels,
|
|
187
373
|
promptAnalysisSampleRate,
|
|
374
|
+
duplicateDetection: !explicitlyOff(process.env[ENV.DUPLICATE_DETECTION]),
|
|
375
|
+
duplicateWindowSeconds: parsePositive(process.env[ENV.DUPLICATE_WINDOW_SECONDS], ENV.DUPLICATE_WINDOW_SECONDS, DEFAULT_DUPLICATE_WINDOW_SECONDS, { max: MAX_DUPLICATE_WINDOW_SECONDS }),
|
|
376
|
+
duplicateCapacity: parsePositive(process.env[ENV.DUPLICATE_CAPACITY], ENV.DUPLICATE_CAPACITY, DEFAULT_DUPLICATE_CAPACITY, { integer: true, max: MAX_DUPLICATE_CAPACITY }),
|
|
377
|
+
duplicateFpRate,
|
|
378
|
+
duplicateGenerations: parsePositive(process.env[ENV.DUPLICATE_GENERATIONS], ENV.DUPLICATE_GENERATIONS, DEFAULT_DUPLICATE_GENERATIONS, { integer: true, max: MAX_DUPLICATE_GENERATIONS }),
|
|
188
379
|
};
|
|
189
380
|
}
|
|
190
381
|
/** 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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"}
|