@frugalco/frugal-sdk-node 0.4.0 → 0.6.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 +91 -17
- 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 +41 -0
- package/dist/config.d.ts.map +1 -1
- package/dist/config.js +232 -53
- package/dist/config.js.map +1 -1
- package/dist/instrument.d.ts +3 -0
- package/dist/instrument.d.ts.map +1 -1
- package/dist/instrument.js +70 -22
- package/dist/instrument.js.map +1 -1
- package/dist/internal-errors.d.ts +16 -0
- package/dist/internal-errors.d.ts.map +1 -0
- package/dist/internal-errors.js +54 -0
- package/dist/internal-errors.js.map +1 -0
- 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 +45 -4
- package/dist/otel.d.ts.map +1 -1
- package/dist/otel.js +233 -24
- package/dist/otel.js.map +1 -1
- package/dist/providers.d.ts +2 -1
- package/dist/providers.d.ts.map +1 -1
- package/dist/providers.js +36 -19
- package/dist/providers.js.map +1 -1
- package/dist/semconv.d.ts +4 -0
- package/dist/semconv.d.ts.map +1 -1
- package/dist/semconv.js +17 -2
- package/dist/semconv.js.map +1 -1
- package/dist/streams.d.ts.map +1 -1
- package/dist/streams.js +6 -5
- package/dist/streams.js.map +1 -1
- package/dist/track.d.ts +6 -3
- package/dist/track.d.ts.map +1 -1
- package/dist/track.js +22 -7
- package/dist/track.js.map +1 -1
- package/dist/wrappers/anthropic.d.ts.map +1 -1
- package/dist/wrappers/anthropic.js +11 -31
- package/dist/wrappers/anthropic.js.map +1 -1
- package/dist/wrappers/batch.d.ts.map +1 -1
- package/dist/wrappers/batch.js +14 -34
- package/dist/wrappers/batch.js.map +1 -1
- package/dist/wrappers/bedrock.d.ts.map +1 -1
- package/dist/wrappers/bedrock.js +11 -30
- package/dist/wrappers/bedrock.js.map +1 -1
- package/dist/wrappers/openai.d.ts.map +1 -1
- package/dist/wrappers/openai.js +10 -31
- package/dist/wrappers/openai.js.map +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -38,7 +38,7 @@ All configuration is via environment variables. No `init()` call, no config obje
|
|
|
38
38
|
|
|
39
39
|
### Required
|
|
40
40
|
|
|
41
|
-
Set both or the wrapper silently returns the client unmodified:
|
|
41
|
+
Set both (or, with `FRUGAL_METRICS_EXPORTER=console`, only `FRUGAL_PROJECT_ID`) or the wrapper silently returns the client unmodified:
|
|
42
42
|
|
|
43
43
|
| Variable | Purpose |
|
|
44
44
|
| ------------------- | ---------------------------------------------------------- |
|
|
@@ -49,24 +49,38 @@ Set both or the wrapper silently returns the client unmodified:
|
|
|
49
49
|
|
|
50
50
|
| Variable | Purpose |
|
|
51
51
|
| ---------------------------------------- | --------------------------------------------------------------------------------------- |
|
|
52
|
+
| `FRUGAL_METRICS_PROJECT_ID` / `FRUGAL_METRICS_API_KEY` | Namespaced overrides read before `FRUGAL_PROJECT_ID` / `FRUGAL_API_KEY`; an empty value falls through. Use them when the host app already owns the bare names. |
|
|
52
53
|
| `FRUGAL_CUSTOMER_ID` | Customer ID issued by Frugal. When unset, the tenant label (auto-injected server-side by VMAuth from the bearer token) identifies the customer and `frugal.customer_id` is omitted. |
|
|
53
54
|
| `FRUGAL_REPO_ROOT` | Absolute path to the source root; the strip prefix for `caller_file`. Defaults to the process working directory. |
|
|
54
|
-
| `FRUGAL_OTEL_EXPORTER_OTLP_ENDPOINT` | OTLP collector URL for Frugal metrics. Defaults to `https://metrics.frugal.co/opentelemetry/v1/metrics
|
|
55
|
-
| `
|
|
56
|
-
| `
|
|
55
|
+
| `FRUGAL_OTEL_EXPORTER_OTLP_ENDPOINT` | OTLP collector URL for Frugal metrics. Defaults to `https://metrics.frugal.co/opentelemetry/v1/metrics`; an empty value falls through to the default. |
|
|
56
|
+
| `FRUGAL_METRICS_EXPORTER` | `otlp` (default) or `console`. `console` prints metrics to stdout through the console exporter for local verification; console mode needs only `FRUGAL_PROJECT_ID`; the API key, endpoint and header variables are ignored. Value is trimmed and case-insensitive; anything else warns once and uses `otlp`. |
|
|
57
|
+
| `FRUGAL_OTEL_EXPORTER_OTLP_HEADERS` | Export auth headers (e.g. `Authorization=Bearer ...`). Overrides the header built from `FRUGAL_METRICS_API_KEY` / `FRUGAL_API_KEY`; an empty or blank (whitespace-only) value is treated as unset and falls through to it. A non-blank value with no `key=value` pairs (e.g. `Authorization: Bearer x`) counts as missing auth, even when an API key is set — it never falls back to the key. Values are percent-decoded (`Bearer%20tok` → `Bearer tok`); a value that fails to decode is kept as-is. |
|
|
58
|
+
| `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
59
|
| `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). |
|
|
60
|
+
| `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. A warning fires only when non-empty entries past the 20th are dropped (a trailing comma does not warn). `other` is reserved and ignored as an entry. See [Custom call sites](#custom-call-sites). |
|
|
59
61
|
| `FRUGAL_PATH_BLOCK_LIST` | Comma-separated repo-relative paths to skip for caller attribution (see below). |
|
|
60
|
-
| `FRUGAL_METRIC_EXPORT_INTERVAL_MS` | Export cadence in milliseconds
|
|
62
|
+
| `FRUGAL_METRIC_EXPORT_INTERVAL_MS` | Export cadence in milliseconds, `1`-`2147483647`. Default `60000`; a larger value warns once and uses the default. |
|
|
61
63
|
| `FRUGAL_MAX_CALL_SITES` | Cardinality cap — max distinct `(caller_file, caller_function)` pairs to attribute. Default `500`. Set to `0` to disable. |
|
|
62
|
-
| `
|
|
64
|
+
| `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. |
|
|
65
|
+
| `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). |
|
|
66
|
+
| `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. A decimal in [0, 1], plain or scientific notation (e.g. `0.25`, `1e-1`); anything else warns once and uses `0.1`. |
|
|
63
67
|
| `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
68
|
| `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
69
|
| `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
|
|
70
|
+
| `FRUGAL_DUPLICATE_FP_RATE` | Target false-positive rate for the duplicate filter. Default `0.01`; clamped to `[1e-6, 0.5]` with a warning. |
|
|
67
71
|
| `FRUGAL_DUPLICATE_GENERATIONS` | Rotating-Bloom generation count. Default `2`, max `16`. |
|
|
68
72
|
| `FRUGAL_DISABLED` | Set to `1` to disable all instrumentation without uninstalling. |
|
|
69
73
|
|
|
74
|
+
The export endpoint and headers come only from `FRUGAL_`-prefixed variables.
|
|
75
|
+
The SDK never reads `OTEL_EXPORTER_OTLP_ENDPOINT` or
|
|
76
|
+
`OTEL_EXPORTER_OTLP_HEADERS`, and it hides `OTEL_EXPORTER_OTLP_HEADERS` and
|
|
77
|
+
`OTEL_EXPORTER_OTLP_METRICS_HEADERS` from the OTel exporter library while
|
|
78
|
+
constructing its exporter (the library would otherwise merge them into every
|
|
79
|
+
request), so the host's own OpenTelemetry headers never reach Frugal and the
|
|
80
|
+
Frugal bearer never reaches the host's collector. The exporter library still
|
|
81
|
+
reads its other standard variables (TLS, compression, timeout, temporality) on
|
|
82
|
+
its own.
|
|
83
|
+
|
|
70
84
|
## What gets captured
|
|
71
85
|
|
|
72
86
|
Every wrapped call emits OTel metrics tagged with the caller's source file and function, the provider, and the model:
|
|
@@ -79,8 +93,9 @@ Every wrapped call emits OTel metrics tagged with the caller's source file and f
|
|
|
79
93
|
| `frugal.gen_ai.cache_read_tokens` | histogram | Prompt cache hits | — |
|
|
80
94
|
| `frugal.gen_ai.cache_write_tokens` | histogram | Prompt cache writes | — |
|
|
81
95
|
| `frugal.gen_ai.reasoning_tokens` | histogram | OpenAI reasoning tokens (o-series), Anthropic thinking tokens| — |
|
|
96
|
+
| `frugal.gen_ai.agent.turns` | histogram | Model-response turns in a successful agent invocation | — |
|
|
82
97
|
| `frugal.gen_ai.errors` | counter | Errors by exception type | `frugal.error.type` |
|
|
83
|
-
| `frugal.gen_ai.internal_errors` | counter | frugal-metrics internal failures (cardinality
|
|
98
|
+
| `frugal.gen_ai.internal_errors` | counter | frugal-metrics internal failures (cardinality caps, bugs) | `frugal.internal_error.stage`, `frugal.internal_error.type`, `frugal.instrument` (on `otel_cardinality_overflow` only) |
|
|
84
99
|
| `frugal.gen_ai.output_cap_utilization` | histogram | `output_tokens / max_tokens` ratio when `max_tokens` is set | `frugal.structured_output_mode` |
|
|
85
100
|
| `frugal.gen_ai.history_depth` | histogram | `messages.length` — every chat call, success or error (100%, not sampled) | — |
|
|
86
101
|
| `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`) |
|
|
@@ -101,9 +116,9 @@ Every metric carries this base attribute set:
|
|
|
101
116
|
- `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
|
|
102
117
|
- `frugal.caller_file`, `frugal.caller_function` — auto-detected from the call stack
|
|
103
118
|
- `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).
|
|
104
|
-
- `gen_ai.request.model`, `gen_ai.response.model` — the model used
|
|
119
|
+
- `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
|
|
105
120
|
- `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)
|
|
106
|
-
- `frugal.batched` — `true` for batch-API paths (OpenAI Batch API submissions; Anthropic Message Batches submissions and results), `false` otherwise
|
|
121
|
+
- `frugal.batched` — `true` for batch-API paths (OpenAI Batch API submissions; Anthropic Message Batches submissions and results), `false` otherwise; on every metric of the call except the `frugal.gen_ai.internal_errors` points
|
|
107
122
|
|
|
108
123
|
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.
|
|
109
124
|
|
|
@@ -171,9 +186,57 @@ try {
|
|
|
171
186
|
|
|
172
187
|
`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.
|
|
173
188
|
|
|
174
|
-
|
|
189
|
+
For an agent invocation, set `operation: "invoke_agent"` and report the number
|
|
190
|
+
of model-response steps completed by the whole run:
|
|
191
|
+
|
|
192
|
+
```ts
|
|
193
|
+
const result = await trackAICall(
|
|
194
|
+
{ operation: "invoke_agent", model: "agent-model" },
|
|
195
|
+
async (report) => {
|
|
196
|
+
const run = await agent.invoke(task);
|
|
197
|
+
report({ agentTurns: run.turnCount });
|
|
198
|
+
return run.output;
|
|
199
|
+
},
|
|
200
|
+
);
|
|
201
|
+
```
|
|
202
|
+
|
|
203
|
+
The manual-handle form uses the same contract:
|
|
204
|
+
|
|
205
|
+
```ts
|
|
206
|
+
const handle = startAICall({ operation: "invoke_agent", model: "agent-model" });
|
|
207
|
+
try {
|
|
208
|
+
const run = await agent.invoke(task);
|
|
209
|
+
handle.end({ agentTurns: run.turnCount });
|
|
210
|
+
} catch (err) {
|
|
211
|
+
handle.fail(err);
|
|
212
|
+
throw err;
|
|
213
|
+
}
|
|
214
|
+
```
|
|
175
215
|
|
|
176
|
-
|
|
216
|
+
Provider wrappers continue to measure individual model calls. Agent frameworks
|
|
217
|
+
use `trackAICall` or `startAICall` to measure the enclosing invocation; dedicated
|
|
218
|
+
framework adapters are not available yet. A turn sample is emitted only for a
|
|
219
|
+
successful `invoke_agent` call with a positive-integer `agentTurns` value.
|
|
220
|
+
|
|
221
|
+
**Avoid double-counting tokens.** The examples above report `agentTurns` only,
|
|
222
|
+
which is correct when the agent's inner model-client calls are already wrapped
|
|
223
|
+
by a supported provider wrapper: those wrappers emit token usage per inner
|
|
224
|
+
call, so also reporting aggregate tokens on the `invoke_agent` call would
|
|
225
|
+
double-count them. Only when the inner calls are **not** wrapped, add the
|
|
226
|
+
aggregate `inputTokens` / `outputTokens` (and the other usage fields):
|
|
227
|
+
|
|
228
|
+
```ts
|
|
229
|
+
// Inner model calls are NOT wrapped by a provider wrapper.
|
|
230
|
+
report({
|
|
231
|
+
agentTurns: run.turnCount,
|
|
232
|
+
inputTokens: run.usage.inputTokens,
|
|
233
|
+
outputTokens: run.usage.outputTokens,
|
|
234
|
+
});
|
|
235
|
+
```
|
|
236
|
+
|
|
237
|
+
**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 except the `frugal.gen_ai.internal_errors` `setup_failed` / `emission` points; 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`.
|
|
238
|
+
|
|
239
|
+
**Usage** — `report(...)` / `end(...)` fields (all optional): `inputTokens`, `outputTokens`, `cacheReadTokens`, `cacheWriteTokens`, `reasoningTokens`, `responseModel`, `agentTurns`.
|
|
177
240
|
|
|
178
241
|
The same env-var configuration, safety guarantees, and exported metrics apply.
|
|
179
242
|
|
|
@@ -269,17 +332,28 @@ It is **opt-in** because installing signal handlers is intrusive. If you run you
|
|
|
269
332
|
|
|
270
333
|
## Verifying the setup
|
|
271
334
|
|
|
272
|
-
Set
|
|
335
|
+
Set `FRUGAL_METRICS_EXPORTER=console` to print the metrics to stdout instead
|
|
336
|
+
of exporting them (console mode needs only `FRUGAL_PROJECT_ID`; the API key, endpoint and header variables are ignored):
|
|
337
|
+
|
|
338
|
+
```bash
|
|
339
|
+
export FRUGAL_PROJECT_ID=my-project
|
|
340
|
+
export FRUGAL_METRICS_EXPORTER=console
|
|
341
|
+
export FRUGAL_METRIC_EXPORT_INTERVAL_MS=5000
|
|
342
|
+
node my_script.js
|
|
343
|
+
```
|
|
344
|
+
|
|
345
|
+
Or point the library at a local OTLP/HTTP collector. An empty endpoint is not a
|
|
346
|
+
console switch: it falls through to the default Frugal endpoint.
|
|
273
347
|
|
|
274
348
|
```bash
|
|
275
|
-
export FRUGAL_API_KEY=
|
|
349
|
+
export FRUGAL_API_KEY=my-key
|
|
276
350
|
export FRUGAL_PROJECT_ID=my-project
|
|
277
|
-
export FRUGAL_OTEL_EXPORTER_OTLP_ENDPOINT=
|
|
351
|
+
export FRUGAL_OTEL_EXPORTER_OTLP_ENDPOINT=http://localhost:4318/v1/metrics
|
|
278
352
|
export FRUGAL_METRIC_EXPORT_INTERVAL_MS=5000
|
|
279
353
|
node my_script.js
|
|
280
354
|
```
|
|
281
355
|
|
|
282
|
-
|
|
356
|
+
The collector should receive metric records with `gen_ai.*` and `frugal.*` attributes every 5 seconds.
|
|
283
357
|
|
|
284
358
|
## License
|
|
285
359
|
|
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
|
@@ -19,11 +19,32 @@ export interface Config {
|
|
|
19
19
|
customLabels: readonly string[];
|
|
20
20
|
repoRoot: string;
|
|
21
21
|
pathBlockList: readonly string[];
|
|
22
|
+
/** Metric exporter: `otlp` (default) or `console` (prints to stdout; endpoint/headers ignored). */
|
|
23
|
+
exporter: "otlp" | "console";
|
|
22
24
|
otlpEndpoint: string;
|
|
25
|
+
/** Resolved header string (display/compat). Only an explicit FRUGAL_OTEL_EXPORTER_OTLP_HEADERS value is parsed and percent-decoded for export. */
|
|
23
26
|
otlpHeaders: string;
|
|
27
|
+
/**
|
|
28
|
+
* The raw API key when the export header comes from it (no explicit
|
|
29
|
+
* FRUGAL_OTEL_EXPORTER_OTLP_HEADERS), else null. The exporter builds
|
|
30
|
+
* `Authorization: Bearer <key>` from it directly, so a key containing `%` or
|
|
31
|
+
* `,` is never decoded or split.
|
|
32
|
+
*/
|
|
33
|
+
otlpApiKey: string | null;
|
|
24
34
|
exportIntervalMs: number;
|
|
25
35
|
/** Max distinct (file, function) combinations to attribute before overflow. 0 = unlimited. */
|
|
26
36
|
maxCallSites: number;
|
|
37
|
+
/**
|
|
38
|
+
* OTel's per-instrument limit on distinct attribute SETS. Beyond it the SDK
|
|
39
|
+
* merges everything into one `{otel.metric.overflow: true}` series with every
|
|
40
|
+
* other attribute dropped. 0 = disabled (effectively unlimited).
|
|
41
|
+
*/
|
|
42
|
+
maxSeriesPerInstrument: number;
|
|
43
|
+
/**
|
|
44
|
+
* Max distinct model strings (request + response share one registry) to
|
|
45
|
+
* attribute before they collapse to the `<overflow>` sentinel. 0 = unlimited.
|
|
46
|
+
*/
|
|
47
|
+
maxModels: number;
|
|
27
48
|
/** Fraction of analyzed calls to emit prompt-analysis metrics for, 0..1. */
|
|
28
49
|
promptAnalysisSampleRate: number;
|
|
29
50
|
duplicateDetection: boolean;
|
|
@@ -32,7 +53,27 @@ export interface Config {
|
|
|
32
53
|
duplicateFpRate: number;
|
|
33
54
|
duplicateGenerations: number;
|
|
34
55
|
}
|
|
56
|
+
/**
|
|
57
|
+
* Load the configuration, or null when the SDK should stay a no-op.
|
|
58
|
+
*
|
|
59
|
+
* Every wrapX() calls this at the customer's module scope, so a throw here
|
|
60
|
+
* crashes the host app before it has run a line of its own code. Nothing in
|
|
61
|
+
* the body is supposed to throw — but it reads the environment and touches the
|
|
62
|
+
* filesystem (process.cwd() raises ENOENT when the working directory has been
|
|
63
|
+
* removed, realpathSync can raise on a broken mount), so the whole thing is
|
|
64
|
+
* contained: any failure disables the SDK with one diagnostic instead of
|
|
65
|
+
* taking the process with it.
|
|
66
|
+
*/
|
|
35
67
|
export declare function loadConfig(): Config | null;
|
|
68
|
+
/**
|
|
69
|
+
* Parse an OTLP `key=value,key=value` header string. Values are
|
|
70
|
+
* percent-decoded like OTel's own env parser (`Bearer%20tok` → `Bearer tok`);
|
|
71
|
+
* keys are kept literal, and a value that fails to decode is kept raw. Parts
|
|
72
|
+
* without `=` or with an empty key are dropped; a string with no pairs at all
|
|
73
|
+
* returns undefined.
|
|
74
|
+
* Same rule in the Python and Go SDKs.
|
|
75
|
+
*/
|
|
76
|
+
export declare function parseHeaders(raw: string | null): Record<string, string> | undefined;
|
|
36
77
|
/** Test-only hook to re-arm the one-shot warning. */
|
|
37
78
|
export declare function resetWarningStateForTests(): void;
|
|
38
79
|
//# sourceMappingURL=config.d.ts.map
|
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":"AAwCA,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,mGAAmG;IACnG,QAAQ,EAAE,MAAM,GAAG,SAAS,CAAC;IAC7B,YAAY,EAAE,MAAM,CAAC;IACrB,kJAAkJ;IAClJ,WAAW,EAAE,MAAM,CAAC;IACpB;;;;;OAKG;IACH,UAAU,EAAE,MAAM,GAAG,IAAI,CAAC;IAC1B,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;AAmMD;;;;;;;;;;GAUG;AACH,wBAAgB,UAAU,IAAI,MAAM,GAAG,IAAI,CAW1C;AAED;;;;;;;GAOG;AACH,wBAAgB,YAAY,CAAC,GAAG,EAAE,MAAM,GAAG,IAAI,GAAG,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,GAAG,SAAS,CAoBnF;AAwUD,qDAAqD;AACrD,wBAAgB,yBAAyB,IAAI,IAAI,CAEhD"}
|