@frugalco/frugal-sdk-node 0.1.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.
Files changed (59) hide show
  1. package/LICENSE +201 -0
  2. package/README.md +227 -0
  3. package/dist/block-hash-lru.d.ts +18 -0
  4. package/dist/block-hash-lru.d.ts.map +1 -0
  5. package/dist/block-hash-lru.js +48 -0
  6. package/dist/block-hash-lru.js.map +1 -0
  7. package/dist/caller.d.ts +23 -0
  8. package/dist/caller.d.ts.map +1 -0
  9. package/dist/caller.js +204 -0
  10. package/dist/caller.js.map +1 -0
  11. package/dist/config.d.ts +21 -0
  12. package/dist/config.d.ts.map +1 -0
  13. package/dist/config.js +165 -0
  14. package/dist/config.js.map +1 -0
  15. package/dist/index.d.ts +6 -0
  16. package/dist/index.d.ts.map +1 -0
  17. package/dist/index.js +5 -0
  18. package/dist/index.js.map +1 -0
  19. package/dist/instrument.d.ts +49 -0
  20. package/dist/instrument.d.ts.map +1 -0
  21. package/dist/instrument.js +194 -0
  22. package/dist/instrument.js.map +1 -0
  23. package/dist/otel.d.ts +27 -0
  24. package/dist/otel.d.ts.map +1 -0
  25. package/dist/otel.js +211 -0
  26. package/dist/otel.js.map +1 -0
  27. package/dist/prompt-analyzer.d.ts +26 -0
  28. package/dist/prompt-analyzer.d.ts.map +1 -0
  29. package/dist/prompt-analyzer.js +210 -0
  30. package/dist/prompt-analyzer.js.map +1 -0
  31. package/dist/semconv.d.ts +52 -0
  32. package/dist/semconv.d.ts.map +1 -0
  33. package/dist/semconv.js +53 -0
  34. package/dist/semconv.js.map +1 -0
  35. package/dist/streams.d.ts +21 -0
  36. package/dist/streams.d.ts.map +1 -0
  37. package/dist/streams.js +85 -0
  38. package/dist/streams.js.map +1 -0
  39. package/dist/track.d.ts +44 -0
  40. package/dist/track.d.ts.map +1 -0
  41. package/dist/track.js +170 -0
  42. package/dist/track.js.map +1 -0
  43. package/dist/wrappers/anthropic.d.ts +2 -0
  44. package/dist/wrappers/anthropic.d.ts.map +1 -0
  45. package/dist/wrappers/anthropic.js +229 -0
  46. package/dist/wrappers/anthropic.js.map +1 -0
  47. package/dist/wrappers/bedrock-models.d.ts +26 -0
  48. package/dist/wrappers/bedrock-models.d.ts.map +1 -0
  49. package/dist/wrappers/bedrock-models.js +320 -0
  50. package/dist/wrappers/bedrock-models.js.map +1 -0
  51. package/dist/wrappers/bedrock.d.ts +2 -0
  52. package/dist/wrappers/bedrock.d.ts.map +1 -0
  53. package/dist/wrappers/bedrock.js +284 -0
  54. package/dist/wrappers/bedrock.js.map +1 -0
  55. package/dist/wrappers/openai.d.ts +2 -0
  56. package/dist/wrappers/openai.d.ts.map +1 -0
  57. package/dist/wrappers/openai.js +347 -0
  58. package/dist/wrappers/openai.js.map +1 -0
  59. package/package.json +60 -0
@@ -0,0 +1,194 @@
1
+ // Shared instrumentation helpers used by per-provider wrappers.
2
+ //
3
+ // A wrapper calls `startCall()` at the top of its closure to resolve the
4
+ // caller and start a timer, then calls `emit.success(...)` on return or
5
+ // `emit.error(...)` on exception. The wrapper must call resolveCaller
6
+ // synchronously from its own frame — don't interpose helper functions between
7
+ // the wrapper frame and resolveCaller, or the caller attribution will be wrong.
8
+ import { consumeCapHitEvent, resolveCaller, } from "./caller.js";
9
+ import { getInstruments } from "./otel.js";
10
+ import { getSharedBlockHashLRU } from "./block-hash-lru.js";
11
+ import { analyzeRequest, shouldSample, structuredOutputMode, } from "./prompt-analyzer.js";
12
+ import { BLOCK_SCHEMA, BLOCK_SYSTEM, BLOCK_TOOLS, BLOCK_USER, FRUGAL_BATCHED, FRUGAL_BLOCK, FRUGAL_CALLER_FILE, FRUGAL_CALLER_FUNCTION, FRUGAL_CUSTOMER_ID, FRUGAL_ERROR_TYPE, FRUGAL_PROJECT_ID, FRUGAL_STABLE, FRUGAL_STRUCTURED_OUTPUT_MODE, GEN_AI_OPERATION_NAME, GEN_AI_REQUEST_MODEL, GEN_AI_RESPONSE_MODEL, GEN_AI_SYSTEM, GEN_AI_TOKEN_TYPE, TOKEN_INPUT, TOKEN_OUTPUT, } from "./semconv.js";
13
+ export function startCall(config, ctx, opts = {}) {
14
+ const instruments = getInstruments(config);
15
+ const caller = opts.caller ??
16
+ resolveCaller({
17
+ skipPackageDirs: ctx.skipPackageDirs,
18
+ blockList: config.pathBlockList,
19
+ repoRoot: config.repoRoot,
20
+ maxCallSites: config.maxCallSites,
21
+ skipFrames: opts.skipFrames ?? 2,
22
+ });
23
+ if (consumeCapHitEvent()) {
24
+ try {
25
+ instruments.internalErrors.add(1, {
26
+ ...(config.customerId ? { [FRUGAL_CUSTOMER_ID]: config.customerId } : {}),
27
+ [FRUGAL_PROJECT_ID]: config.projectId,
28
+ "frugal.internal_error.stage": "cardinality_cap_hit",
29
+ });
30
+ }
31
+ catch {
32
+ // never let our own metric emission break a customer call
33
+ }
34
+ }
35
+ return new CallEmitter(config, ctx, caller, instruments, nowSeconds());
36
+ }
37
+ function nowSeconds() {
38
+ // performance.now() returns ms with sub-ms resolution.
39
+ return performance.now() / 1000;
40
+ }
41
+ export class CallEmitter {
42
+ config;
43
+ ctx;
44
+ caller;
45
+ instruments;
46
+ startSec;
47
+ finalized = false;
48
+ constructor(config, ctx, caller, instruments, startSec) {
49
+ this.config = config;
50
+ this.ctx = ctx;
51
+ this.caller = caller;
52
+ this.instruments = instruments;
53
+ this.startSec = startSec;
54
+ }
55
+ success(usage) {
56
+ if (this.finalized)
57
+ return;
58
+ this.finalized = true;
59
+ const attrs = this.baseAttrs();
60
+ if (usage.responseModel) {
61
+ attrs[GEN_AI_RESPONSE_MODEL] = usage.responseModel;
62
+ }
63
+ const duration = nowSeconds() - this.startSec;
64
+ this.instruments.operationDuration.record(duration, attrs);
65
+ this.instruments.calls.add(1, attrs);
66
+ if (nonNull(usage.inputTokens)) {
67
+ this.instruments.tokenUsage.record(usage.inputTokens, {
68
+ ...attrs,
69
+ [GEN_AI_TOKEN_TYPE]: TOKEN_INPUT,
70
+ });
71
+ }
72
+ if (nonNull(usage.outputTokens)) {
73
+ this.instruments.tokenUsage.record(usage.outputTokens, {
74
+ ...attrs,
75
+ [GEN_AI_TOKEN_TYPE]: TOKEN_OUTPUT,
76
+ });
77
+ }
78
+ if (nonNull(usage.cacheReadTokens)) {
79
+ this.instruments.cacheReadTokens.record(usage.cacheReadTokens, attrs);
80
+ }
81
+ if (nonNull(usage.cacheWriteTokens)) {
82
+ this.instruments.cacheWriteTokens.record(usage.cacheWriteTokens, attrs);
83
+ }
84
+ if (nonNull(usage.reasoningTokens)) {
85
+ this.instruments.reasoningTokens.record(usage.reasoningTokens, attrs);
86
+ }
87
+ this.recordCapUtilization(usage, attrs);
88
+ this.maybeAnalyze(attrs);
89
+ }
90
+ recordCapUtilization(usage, attrs) {
91
+ const payload = this.ctx.requestPayload;
92
+ if (!payload || !nonNull(payload.maxTokens) || !nonNull(usage.outputTokens)) {
93
+ return;
94
+ }
95
+ if (payload.maxTokens <= 0)
96
+ return;
97
+ const ratio = Math.min(1, usage.outputTokens / payload.maxTokens);
98
+ this.instruments.outputCapUtilization.record(ratio, {
99
+ ...attrs,
100
+ [FRUGAL_STRUCTURED_OUTPUT_MODE]: structuredOutputMode(payload),
101
+ });
102
+ }
103
+ maybeAnalyze(attrs) {
104
+ const payload = this.ctx.requestPayload;
105
+ if (!payload)
106
+ return;
107
+ const callSiteId = `${this.caller.file}:${this.caller.function}`;
108
+ if (!shouldSample(callSiteId, this.config.promptAnalysisSampleRate))
109
+ return;
110
+ let result;
111
+ try {
112
+ result = analyzeRequest(payload);
113
+ }
114
+ catch {
115
+ return;
116
+ }
117
+ if (nonNull(result.historyDepth)) {
118
+ this.instruments.historyDepth.record(result.historyDepth, attrs);
119
+ }
120
+ if (nonNull(result.fewShotCount)) {
121
+ this.instruments.fewShotCount.record(result.fewShotCount, attrs);
122
+ }
123
+ if (nonNull(result.noiseRatio)) {
124
+ this.instruments.noiseRatio.record(result.noiseRatio, attrs);
125
+ }
126
+ if (result.structuredOutputHint) {
127
+ this.instruments.structuredOutputHint.add(1, attrs);
128
+ }
129
+ const lru = getSharedBlockHashLRU(this.config.maxCallSites);
130
+ const previous = lru.get(callSiteId);
131
+ const current = {
132
+ systemHash: result.systemHash,
133
+ userHash: result.userHash,
134
+ toolsHash: result.toolsHash,
135
+ schemaHash: result.schemaHash,
136
+ };
137
+ if (previous !== undefined) {
138
+ this.recordStable(attrs, BLOCK_SYSTEM, previous.systemHash, current.systemHash);
139
+ this.recordStable(attrs, BLOCK_USER, previous.userHash, current.userHash);
140
+ this.recordStable(attrs, BLOCK_TOOLS, previous.toolsHash, current.toolsHash);
141
+ this.recordStable(attrs, BLOCK_SCHEMA, previous.schemaHash, current.schemaHash);
142
+ }
143
+ lru.set(callSiteId, current);
144
+ }
145
+ recordStable(attrs, block, prev, curr) {
146
+ // Only emit when both calls had this block — comparing absent-vs-present
147
+ // would conflate "user didn't send a system prompt this time" with churn.
148
+ if (prev === null || curr === null)
149
+ return;
150
+ this.instruments.cachePrefixStable.add(1, {
151
+ ...attrs,
152
+ [FRUGAL_BLOCK]: block,
153
+ [FRUGAL_STABLE]: prev === curr,
154
+ });
155
+ }
156
+ error(err) {
157
+ if (this.finalized)
158
+ return;
159
+ this.finalized = true;
160
+ const attrs = this.baseAttrs();
161
+ attrs[FRUGAL_ERROR_TYPE] = errorName(err);
162
+ const duration = nowSeconds() - this.startSec;
163
+ this.instruments.operationDuration.record(duration, attrs);
164
+ this.instruments.calls.add(1, attrs);
165
+ this.instruments.errors.add(1, attrs);
166
+ }
167
+ baseAttrs() {
168
+ const attrs = {
169
+ [GEN_AI_SYSTEM]: this.ctx.system,
170
+ [GEN_AI_OPERATION_NAME]: this.ctx.operation,
171
+ [GEN_AI_REQUEST_MODEL]: this.ctx.requestModel,
172
+ [FRUGAL_PROJECT_ID]: this.config.projectId,
173
+ [FRUGAL_CALLER_FILE]: this.caller.file,
174
+ [FRUGAL_CALLER_FUNCTION]: this.caller.function,
175
+ [FRUGAL_BATCHED]: this.ctx.batched ?? false,
176
+ };
177
+ if (this.config.customerId) {
178
+ attrs[FRUGAL_CUSTOMER_ID] = this.config.customerId;
179
+ }
180
+ return attrs;
181
+ }
182
+ }
183
+ function nonNull(v) {
184
+ return v !== null && v !== undefined;
185
+ }
186
+ function errorName(err) {
187
+ if (err && typeof err === "object" && "name" in err && typeof err.name === "string") {
188
+ return err.name;
189
+ }
190
+ if (err instanceof Error)
191
+ return err.constructor.name;
192
+ return "UnknownError";
193
+ }
194
+ //# sourceMappingURL=instrument.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"instrument.js","sourceRoot":"","sources":["../src/instrument.ts"],"names":[],"mappings":"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"}
package/dist/otel.d.ts ADDED
@@ -0,0 +1,27 @@
1
+ import type { Counter, Histogram } from "@opentelemetry/api";
2
+ import { MeterProvider, type MetricReader } from "@opentelemetry/sdk-metrics";
3
+ import type { Config } from "./config.js";
4
+ export interface Instruments {
5
+ meterProvider: MeterProvider;
6
+ operationDuration: Histogram;
7
+ tokenUsage: Histogram;
8
+ calls: Counter;
9
+ cacheReadTokens: Histogram;
10
+ cacheWriteTokens: Histogram;
11
+ reasoningTokens: Histogram;
12
+ errors: Counter;
13
+ internalErrors: Counter;
14
+ outputCapUtilization: Histogram;
15
+ cachePrefixStable: Counter;
16
+ historyDepth: Histogram;
17
+ fewShotCount: Histogram;
18
+ noiseRatio: Histogram;
19
+ structuredOutputHint: Counter;
20
+ }
21
+ export declare function getInstruments(config: Config): Instruments;
22
+ export declare function buildFromReader(config: Config, reader: MetricReader): Instruments;
23
+ /** Test-only hook — wipe the cached instruments between tests. */
24
+ export declare function resetInstrumentsForTests(): void;
25
+ /** Test-only hook — install pre-built instruments (e.g. with InMemory reader). */
26
+ export declare function setInstrumentsForTests(instruments: Instruments): void;
27
+ //# sourceMappingURL=otel.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"otel.d.ts","sourceRoot":"","sources":["../src/otel.ts"],"names":[],"mappings":"AAOA,OAAO,KAAK,EAAuB,OAAO,EAAE,SAAS,EAAS,MAAM,oBAAoB,CAAC;AAOzF,OAAO,EAGL,aAAa,EAGb,KAAK,YAAY,EAElB,MAAM,4BAA4B,CAAC;AAEpC,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,aAAa,CAAC;AAwB1C,MAAM,WAAW,WAAW;IAC1B,aAAa,EAAE,aAAa,CAAC;IAC7B,iBAAiB,EAAE,SAAS,CAAC;IAC7B,UAAU,EAAE,SAAS,CAAC;IACtB,KAAK,EAAE,OAAO,CAAC;IACf,eAAe,EAAE,SAAS,CAAC;IAC3B,gBAAgB,EAAE,SAAS,CAAC;IAC5B,eAAe,EAAE,SAAS,CAAC;IAC3B,MAAM,EAAE,OAAO,CAAC;IAChB,cAAc,EAAE,OAAO,CAAC;IACxB,oBAAoB,EAAE,SAAS,CAAC;IAChC,iBAAiB,EAAE,OAAO,CAAC;IAC3B,YAAY,EAAE,SAAS,CAAC;IACxB,YAAY,EAAE,SAAS,CAAC;IACxB,UAAU,EAAE,SAAS,CAAC;IACtB,oBAAoB,EAAE,OAAO,CAAC;CAC/B;AAID,wBAAgB,cAAc,CAAC,MAAM,EAAE,MAAM,GAAG,WAAW,CAI1D;AAOD,wBAAgB,eAAe,CAAC,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,YAAY,GAAG,WAAW,CAyGjF;AA4ED,kEAAkE;AAClE,wBAAgB,wBAAwB,IAAI,IAAI,CAO/C;AAED,kFAAkF;AAClF,wBAAgB,sBAAsB,CAAC,WAAW,EAAE,WAAW,GAAG,IAAI,CAErE"}
package/dist/otel.js ADDED
@@ -0,0 +1,211 @@
1
+ // OpenTelemetry meter provider + OTLP exporter setup.
2
+ //
3
+ // Lazy init on first wrap call. We own our own MeterProvider (not the global
4
+ // one) so we don't conflict with whatever the customer may have already
5
+ // configured. Metrics go to a Frugal-namespaced provider with its own OTLP
6
+ // exporter.
7
+ // Use the protobuf OTLP exporter: VictoriaMetrics (and other strict OTLP
8
+ // backends) reject application/json on /v1/metrics with HTTP 400. The proto
9
+ // package has the same exporter API but ships application/x-protobuf, which
10
+ // is the broadly-supported OTLP wire format.
11
+ import { OTLPMetricExporter } from "@opentelemetry/exporter-metrics-otlp-proto";
12
+ import { Resource } from "@opentelemetry/resources";
13
+ import { ConsoleMetricExporter, ExplicitBucketHistogramAggregation, MeterProvider, PeriodicExportingMetricReader, View, } from "@opentelemetry/sdk-metrics";
14
+ import { FRUGAL_COMPONENT, FRUGAL_CUSTOMER_ID, FRUGAL_PROJECT_ID, METRIC_CACHE_PREFIX_STABLE, METRIC_CACHE_READ_TOKENS, METRIC_CACHE_WRITE_TOKENS, METRIC_CALLS, METRIC_ERRORS, METRIC_FEW_SHOT_COUNT, METRIC_HISTORY_DEPTH, METRIC_NOISE_RATIO, METRIC_OPERATION_DURATION, METRIC_INTERNAL_ERRORS, METRIC_OUTPUT_CAP_UTILIZATION, METRIC_REASONING_TOKENS, METRIC_STRUCTURED_OUTPUT_HINT, METRIC_TOKEN_USAGE, } from "./semconv.js";
15
+ const INSTRUMENTATION_NAME = "frugal-metrics";
16
+ const INSTRUMENTATION_VERSION = "0.1.0";
17
+ let cached = null;
18
+ export function getInstruments(config) {
19
+ if (cached !== null)
20
+ return cached;
21
+ cached = buildInstruments(config);
22
+ return cached;
23
+ }
24
+ function buildInstruments(config) {
25
+ const reader = buildReader(config);
26
+ return buildFromReader(config, reader);
27
+ }
28
+ export function buildFromReader(config, reader) {
29
+ // service.name takes the customer suffix when configured, otherwise stays
30
+ // flat — the tenant label (auto-injected by VMAuth from the bearer token)
31
+ // is the canonical customer identifier on the server side, so a missing
32
+ // customerId is not a problem.
33
+ const serviceName = config.customerId
34
+ ? `frugal-metrics-${config.customerId}`
35
+ : "frugal-metrics";
36
+ const resourceAttrs = {
37
+ "service.name": serviceName,
38
+ [FRUGAL_PROJECT_ID]: config.projectId,
39
+ };
40
+ if (config.customerId) {
41
+ resourceAttrs[FRUGAL_CUSTOMER_ID] = config.customerId;
42
+ }
43
+ const resource = new Resource(resourceAttrs);
44
+ const provider = new MeterProvider({
45
+ resource,
46
+ readers: [reader],
47
+ // Override OTel's default histogram bucket boundaries for operation
48
+ // duration. The default `[0,5,10,25,50,75,100,250,500,750,1000,2500,5000,
49
+ // 7500,10000]` is tuned for long-running operations; LLM calls almost
50
+ // always finish in 0.05s–30s, so the default sticks every sample in the
51
+ // (0,5] bucket and histogram_quantile() returns the same P50/P95 for
52
+ // every call site. These boundaries give visible P50/P95/P99 spread for
53
+ // realistic LLM workloads.
54
+ views: [
55
+ new View({
56
+ instrumentName: METRIC_OPERATION_DURATION,
57
+ aggregation: new ExplicitBucketHistogramAggregation([
58
+ 0.05, 0.1, 0.25, 0.5, 0.75, 1, 1.5, 2, 3, 5, 7.5, 10, 15, 20, 30, 60,
59
+ ]),
60
+ }),
61
+ ],
62
+ });
63
+ const meter = provider.getMeter(INSTRUMENTATION_NAME, INSTRUMENTATION_VERSION);
64
+ const raw = {
65
+ meterProvider: provider,
66
+ operationDuration: meter.createHistogram(METRIC_OPERATION_DURATION, {
67
+ unit: "s",
68
+ description: "Duration of AI client operations.",
69
+ }),
70
+ tokenUsage: meter.createHistogram(METRIC_TOKEN_USAGE, {
71
+ unit: "{token}",
72
+ description: "Token usage reported by the provider.",
73
+ }),
74
+ calls: meter.createCounter(METRIC_CALLS, {
75
+ unit: "{call}",
76
+ description: "Count of AI client operation calls.",
77
+ }),
78
+ cacheReadTokens: meter.createHistogram(METRIC_CACHE_READ_TOKENS, {
79
+ unit: "{token}",
80
+ description: "Tokens served from provider prompt cache.",
81
+ }),
82
+ cacheWriteTokens: meter.createHistogram(METRIC_CACHE_WRITE_TOKENS, {
83
+ unit: "{token}",
84
+ description: "Tokens written to provider prompt cache.",
85
+ }),
86
+ reasoningTokens: meter.createHistogram(METRIC_REASONING_TOKENS, {
87
+ unit: "{token}",
88
+ description: "Reasoning / extended thinking tokens billed at output rate.",
89
+ }),
90
+ errors: meter.createCounter(METRIC_ERRORS, {
91
+ unit: "{error}",
92
+ description: "Count of errors raised by AI client operations.",
93
+ }),
94
+ internalErrors: meter.createCounter(METRIC_INTERNAL_ERRORS, {
95
+ unit: "{error}",
96
+ description: "Count of frugal-metrics internal failures (instrumentation bugs).",
97
+ }),
98
+ outputCapUtilization: meter.createHistogram(METRIC_OUTPUT_CAP_UTILIZATION, {
99
+ unit: "1",
100
+ description: "Ratio of output_tokens to the request's max_tokens cap (0 to 1).",
101
+ }),
102
+ cachePrefixStable: meter.createCounter(METRIC_CACHE_PREFIX_STABLE, {
103
+ unit: "{call}",
104
+ description: "Per-call-site comparison of a request block's hash to the previous call. Stable=true when the hash matches.",
105
+ }),
106
+ historyDepth: meter.createHistogram(METRIC_HISTORY_DEPTH, {
107
+ unit: "{message}",
108
+ description: "Number of messages in the request payload.",
109
+ }),
110
+ fewShotCount: meter.createHistogram(METRIC_FEW_SHOT_COUNT, {
111
+ unit: "{example}",
112
+ description: "Count of consecutive user/assistant pairs preceding the final user message in single-turn calls.",
113
+ }),
114
+ noiseRatio: meter.createHistogram(METRIC_NOISE_RATIO, {
115
+ unit: "1",
116
+ description: "Ratio of noise characters (HTML tags, base64 runs, large whitespace) to total input characters.",
117
+ }),
118
+ structuredOutputHint: meter.createCounter(METRIC_STRUCTURED_OUTPUT_HINT, {
119
+ unit: "{call}",
120
+ description: "Count of calls whose prompt asks for structured output in prose without using tools or response_format.",
121
+ }),
122
+ };
123
+ return applyComponentFanout(raw, config.components);
124
+ }
125
+ // When FRUGAL_COMPONENTS is set, every per-call gen-AI metric is emitted once
126
+ // without a component label (the canonical project-level series) AND once per
127
+ // component (tagged with frugal.component). So usage can be queried for the
128
+ // whole project (component absent) or split by component, with no double-count
129
+ // of the project total. internalErrors is excluded — it's a process-level
130
+ // diagnostic, not per-component attributable.
131
+ function applyComponentFanout(raw, components) {
132
+ if (components.length === 0)
133
+ return raw;
134
+ return {
135
+ ...raw,
136
+ operationDuration: fanoutHistogram(raw.operationDuration, components),
137
+ tokenUsage: fanoutHistogram(raw.tokenUsage, components),
138
+ calls: fanoutCounter(raw.calls, components),
139
+ cacheReadTokens: fanoutHistogram(raw.cacheReadTokens, components),
140
+ cacheWriteTokens: fanoutHistogram(raw.cacheWriteTokens, components),
141
+ reasoningTokens: fanoutHistogram(raw.reasoningTokens, components),
142
+ errors: fanoutCounter(raw.errors, components),
143
+ outputCapUtilization: fanoutHistogram(raw.outputCapUtilization, components),
144
+ cachePrefixStable: fanoutCounter(raw.cachePrefixStable, components),
145
+ historyDepth: fanoutHistogram(raw.historyDepth, components),
146
+ fewShotCount: fanoutHistogram(raw.fewShotCount, components),
147
+ noiseRatio: fanoutHistogram(raw.noiseRatio, components),
148
+ structuredOutputHint: fanoutCounter(raw.structuredOutputHint, components),
149
+ };
150
+ }
151
+ function fanoutCounter(inner, components) {
152
+ return {
153
+ add(value, attributes, context) {
154
+ inner.add(value, attributes, context);
155
+ for (const component of components) {
156
+ inner.add(value, { ...attributes, [FRUGAL_COMPONENT]: component }, context);
157
+ }
158
+ },
159
+ };
160
+ }
161
+ function fanoutHistogram(inner, components) {
162
+ return {
163
+ record(value, attributes, context) {
164
+ inner.record(value, attributes, context);
165
+ for (const component of components) {
166
+ inner.record(value, { ...attributes, [FRUGAL_COMPONENT]: component }, context);
167
+ }
168
+ },
169
+ };
170
+ }
171
+ function buildReader(config) {
172
+ const exporter = config.otlpEndpoint
173
+ ? new OTLPMetricExporter({
174
+ url: config.otlpEndpoint,
175
+ headers: parseHeaders(config.otlpHeaders),
176
+ })
177
+ : new ConsoleMetricExporter();
178
+ return new PeriodicExportingMetricReader({
179
+ exporter,
180
+ exportIntervalMillis: config.exportIntervalMs,
181
+ });
182
+ }
183
+ function parseHeaders(raw) {
184
+ if (!raw)
185
+ return undefined;
186
+ const out = {};
187
+ for (const part of raw.split(",")) {
188
+ const trimmed = part.trim();
189
+ if (!trimmed)
190
+ continue;
191
+ const eq = trimmed.indexOf("=");
192
+ if (eq < 0)
193
+ continue;
194
+ out[trimmed.slice(0, eq).trim()] = trimmed.slice(eq + 1).trim();
195
+ }
196
+ return Object.keys(out).length > 0 ? out : undefined;
197
+ }
198
+ /** Test-only hook — wipe the cached instruments between tests. */
199
+ export function resetInstrumentsForTests() {
200
+ if (cached) {
201
+ cached.meterProvider.shutdown().catch(() => {
202
+ /* ignore */
203
+ });
204
+ }
205
+ cached = null;
206
+ }
207
+ /** Test-only hook — install pre-built instruments (e.g. with InMemory reader). */
208
+ export function setInstrumentsForTests(instruments) {
209
+ cached = instruments;
210
+ }
211
+ //# sourceMappingURL=otel.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"otel.js","sourceRoot":"","sources":["../src/otel.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,26 @@
1
+ export interface RequestPayload {
2
+ /** Provider-specific messages array (OpenAI chat / Anthropic messages). */
3
+ messages?: unknown;
4
+ /** Provider-specific tools array. */
5
+ tools?: unknown;
6
+ /** OpenAI response_format object, if any. */
7
+ responseFormat?: unknown;
8
+ /** OpenAI max_tokens / max_completion_tokens, or Anthropic max_tokens. */
9
+ maxTokens?: number | null;
10
+ /** Optional system prompt for Anthropic (passed alongside messages). */
11
+ system?: unknown;
12
+ }
13
+ export interface AnalysisResult {
14
+ systemHash: string | null;
15
+ userHash: string | null;
16
+ toolsHash: string | null;
17
+ schemaHash: string | null;
18
+ historyDepth: number | null;
19
+ fewShotCount: number | null;
20
+ noiseRatio: number | null;
21
+ structuredOutputHint: boolean;
22
+ }
23
+ export declare function analyzeRequest(payload: RequestPayload): AnalysisResult;
24
+ export declare function structuredOutputMode(payload: RequestPayload): string;
25
+ export declare function shouldSample(callSiteId: string, sampleRate: number, nowMs?: number): boolean;
26
+ //# sourceMappingURL=prompt-analyzer.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"prompt-analyzer.d.ts","sourceRoot":"","sources":["../src/prompt-analyzer.ts"],"names":[],"mappings":"AAeA,MAAM,WAAW,cAAc;IAC7B,2EAA2E;IAC3E,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,qCAAqC;IACrC,KAAK,CAAC,EAAE,OAAO,CAAC;IAChB,6CAA6C;IAC7C,cAAc,CAAC,EAAE,OAAO,CAAC;IACzB,0EAA0E;IAC1E,SAAS,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IAC1B,wEAAwE;IACxE,MAAM,CAAC,EAAE,OAAO,CAAC;CAClB;AAED,MAAM,WAAW,cAAc;IAC7B,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;IAC1B,YAAY,EAAE,MAAM,GAAG,IAAI,CAAC;IAC5B,YAAY,EAAE,MAAM,GAAG,IAAI,CAAC;IAC5B,UAAU,EAAE,MAAM,GAAG,IAAI,CAAC;IAC1B,oBAAoB,EAAE,OAAO,CAAC;CAC/B;AAED,wBAAgB,cAAc,CAAC,OAAO,EAAE,cAAc,GAAG,cAAc,CActE;AAED,wBAAgB,oBAAoB,CAAC,OAAO,EAAE,cAAc,GAAG,MAAM,CAWpE;AAyKD,wBAAgB,YAAY,CAC1B,UAAU,EAAE,MAAM,EAClB,UAAU,EAAE,MAAM,EAClB,KAAK,GAAE,MAAmB,GACzB,OAAO,CAYT"}