imprint-mcp 0.6.2 → 0.6.4
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 +8 -1
- package/package.json +1 -1
- package/prompts/audit-agent.md +6 -5
- package/prompts/build-planning.md +2 -2
- package/prompts/compile-agent.md +6 -4
- package/prompts/live-verifier-agent.md +24 -11
- package/src/cli.ts +41 -6
- package/src/imprint/agent.ts +7 -2
- package/src/imprint/audit.ts +255 -42
- package/src/imprint/auth-compile-tools.ts +64 -13
- package/src/imprint/claude-cli-compile.ts +43 -7
- package/src/imprint/codex-cli-compile.ts +205 -129
- package/src/imprint/compile-agent-types.ts +25 -2
- package/src/imprint/compile-agent.ts +197 -40
- package/src/imprint/compile-tools.ts +164 -22
- package/src/imprint/inject-listener.ts +31 -3
- package/src/imprint/live-verifier.ts +167 -6
- package/src/imprint/llm.ts +7 -3
- package/src/imprint/mcp-compile-server.ts +188 -83
- package/src/imprint/mcp-server.ts +16 -1
- package/src/imprint/probe-backends.ts +60 -25
- package/src/imprint/react-flight-limits.ts +31 -0
- package/src/imprint/record.ts +657 -46
- package/src/imprint/redact.ts +240 -30
- package/src/imprint/response-body-intent.ts +73 -0
- package/src/imprint/response-body-lifecycle.ts +32 -0
- package/src/imprint/response-body-stream.ts +704 -0
- package/src/imprint/teach-state.ts +52 -2
- package/src/imprint/teach.ts +94 -69
- package/src/imprint/tracing.ts +60 -238
package/src/imprint/tracing.ts
CHANGED
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
import { AsyncLocalStorage } from 'node:async_hooks';
|
|
2
1
|
import {
|
|
3
2
|
MimeType,
|
|
4
3
|
type NodeTracerProvider,
|
|
@@ -15,26 +14,6 @@ type TraceKind = OpenInferenceSpanKind | `${OpenInferenceSpanKind}`;
|
|
|
15
14
|
type TraceAttributes = Record<string, unknown>;
|
|
16
15
|
type TraceLlmMessage = { role?: string; content?: string };
|
|
17
16
|
|
|
18
|
-
// ---------------------------------------------------------------------------
|
|
19
|
-
// Cost accumulator — rolls up LLM costs from child spans to a parent span.
|
|
20
|
-
// ---------------------------------------------------------------------------
|
|
21
|
-
interface CostAccumulator {
|
|
22
|
-
inputTokens: number;
|
|
23
|
-
outputTokens: number;
|
|
24
|
-
cacheReadTokens: number;
|
|
25
|
-
cacheWriteTokens: number;
|
|
26
|
-
uncachedInputCost: number;
|
|
27
|
-
cacheReadCost: number;
|
|
28
|
-
cacheWriteCost: number;
|
|
29
|
-
completionCost: number;
|
|
30
|
-
}
|
|
31
|
-
|
|
32
|
-
const costAccumulatorStorage = new AsyncLocalStorage<CostAccumulator>();
|
|
33
|
-
|
|
34
|
-
function getActiveCostAccumulator(): CostAccumulator | undefined {
|
|
35
|
-
return costAccumulatorStorage.getStore();
|
|
36
|
-
}
|
|
37
|
-
|
|
38
17
|
let provider: NodeTracerProvider | null = null;
|
|
39
18
|
let attemptedInit = false;
|
|
40
19
|
let suppressInit = false;
|
|
@@ -49,8 +28,6 @@ export function suppressTracingInit(): void {
|
|
|
49
28
|
}
|
|
50
29
|
const DEFAULT_TRACE_IO_MAX_CHARS = 50_000;
|
|
51
30
|
const DEFAULT_TRACE_ERROR_MAX_CHARS = 2000;
|
|
52
|
-
const CACHE_READ_MULTIPLIER = 0.1;
|
|
53
|
-
const CACHE_WRITE_MULTIPLIER = 1.25;
|
|
54
31
|
|
|
55
32
|
function isTracingEnabled(): boolean {
|
|
56
33
|
return (
|
|
@@ -82,6 +59,12 @@ function validateTracingUrl(raw: string | undefined): string | undefined {
|
|
|
82
59
|
function ensureTracingInitialized(): void {
|
|
83
60
|
if (attemptedInit || suppressInit || !isTracingEnabled()) return;
|
|
84
61
|
attemptedInit = true;
|
|
62
|
+
const legacyCostEnv = legacyTraceCostEnvNames();
|
|
63
|
+
if (legacyCostEnv.length > 0) {
|
|
64
|
+
process.stderr.write(
|
|
65
|
+
`[imprint] warning: ${legacyCostEnv.join(', ')} ${legacyCostEnv.length === 1 ? 'is' : 'are'} no longer used; configure model pricing in Phoenix under Settings → Models\n`,
|
|
66
|
+
);
|
|
67
|
+
}
|
|
85
68
|
// The OTEL SDK default is 128 attributes per span. getLLMAttributes() flattens
|
|
86
69
|
// each input message into ~2+ attributes (role, content, tool_calls…), so a
|
|
87
70
|
// 60-message conversation exceeds the cap and silently drops later attributes
|
|
@@ -104,6 +87,20 @@ export function traceBatchEnabled(value: string | undefined): boolean {
|
|
|
104
87
|
return value === undefined ? true : isTruthy(value);
|
|
105
88
|
}
|
|
106
89
|
|
|
90
|
+
/** Legacy local-pricing variables retained only to produce an upgrade warning. */
|
|
91
|
+
export function legacyTraceCostEnvNames(
|
|
92
|
+
env: Record<string, string | undefined> = process.env,
|
|
93
|
+
): string[] {
|
|
94
|
+
return Object.keys(env)
|
|
95
|
+
.filter(
|
|
96
|
+
(name) =>
|
|
97
|
+
/^IMPRINT_TRACE_(?:INPUT|PROMPT|OUTPUT|COMPLETION)_USD_PER_1M$/.test(name) ||
|
|
98
|
+
/^IMPRINT_TRACE_COST_.+_(?:INPUT|PROMPT|OUTPUT|COMPLETION)_USD_PER_1M$/.test(name),
|
|
99
|
+
)
|
|
100
|
+
.filter((name) => env[name] !== undefined && env[name] !== '')
|
|
101
|
+
.sort();
|
|
102
|
+
}
|
|
103
|
+
|
|
107
104
|
export function traceLlmIoEnabled(): boolean {
|
|
108
105
|
if (process.env.IMPRINT_TRACE_LLM_IO !== undefined)
|
|
109
106
|
return isTruthy(process.env.IMPRINT_TRACE_LLM_IO);
|
|
@@ -163,12 +160,9 @@ export function resolveTraceTokenCount(
|
|
|
163
160
|
*
|
|
164
161
|
* Providers (Anthropic API and the claude CLI alike) report `usage.input_tokens`
|
|
165
162
|
* as the *uncached* portion only — the cached bulk lives in the separate cache
|
|
166
|
-
* counts. `
|
|
167
|
-
*
|
|
168
|
-
*
|
|
169
|
-
* here instead of feeding the bare uncached count (which billed the cached bulk
|
|
170
|
-
* at the full input rate, or mislabeled the token count). Returns null when the
|
|
171
|
-
* uncached count itself is unknown.
|
|
163
|
+
* counts. `llm.token_count.prompt` must reflect the whole prompt so Phoenix can
|
|
164
|
+
* combine it with the detailed cache token counts and calculate cost server-side.
|
|
165
|
+
* Returns null when the uncached count itself is unknown.
|
|
172
166
|
*/
|
|
173
167
|
export function totalPromptTokens(
|
|
174
168
|
uncachedInputTokens: number | null | undefined,
|
|
@@ -179,33 +173,6 @@ export function totalPromptTokens(
|
|
|
179
173
|
return uncachedInputTokens + (cacheReadTokens ?? 0) + (cacheWriteTokens ?? 0);
|
|
180
174
|
}
|
|
181
175
|
|
|
182
|
-
const DEFAULT_MODEL_RATES: Record<string, { inputUsdPer1M: number; outputUsdPer1M: number }> = {
|
|
183
|
-
'claude-opus-4-8': { inputUsdPer1M: 5, outputUsdPer1M: 25 },
|
|
184
|
-
'claude-opus-4-7': { inputUsdPer1M: 5, outputUsdPer1M: 25 },
|
|
185
|
-
'claude-opus-4-6': { inputUsdPer1M: 5, outputUsdPer1M: 25 },
|
|
186
|
-
'claude-opus-4-5': { inputUsdPer1M: 5, outputUsdPer1M: 25 },
|
|
187
|
-
'claude-opus-4-1': { inputUsdPer1M: 15, outputUsdPer1M: 75 },
|
|
188
|
-
'claude-sonnet-4-6': { inputUsdPer1M: 3, outputUsdPer1M: 15 },
|
|
189
|
-
'claude-sonnet-4-5': { inputUsdPer1M: 3, outputUsdPer1M: 15 },
|
|
190
|
-
'claude-haiku-4-5': { inputUsdPer1M: 1, outputUsdPer1M: 5 },
|
|
191
|
-
};
|
|
192
|
-
|
|
193
|
-
export function traceLlmCostRates(
|
|
194
|
-
providerName: string,
|
|
195
|
-
modelName?: string,
|
|
196
|
-
): { inputUsdPer1M: number; outputUsdPer1M: number } | null {
|
|
197
|
-
const inputUsdPer1M = envNumber(rateEnvNames(providerName, modelName, 'INPUT'));
|
|
198
|
-
const outputUsdPer1M = envNumber(rateEnvNames(providerName, modelName, 'OUTPUT'));
|
|
199
|
-
if (inputUsdPer1M !== null && outputUsdPer1M !== null) {
|
|
200
|
-
return { inputUsdPer1M, outputUsdPer1M };
|
|
201
|
-
}
|
|
202
|
-
if (modelName) {
|
|
203
|
-
const defaultRate = DEFAULT_MODEL_RATES[modelName];
|
|
204
|
-
if (defaultRate) return defaultRate;
|
|
205
|
-
}
|
|
206
|
-
return null;
|
|
207
|
-
}
|
|
208
|
-
|
|
209
176
|
export function traceInputOutputAttributes(
|
|
210
177
|
direction: 'input' | 'output',
|
|
211
178
|
value: string,
|
|
@@ -274,59 +241,6 @@ export async function traced<T>(
|
|
|
274
241
|
);
|
|
275
242
|
}
|
|
276
243
|
|
|
277
|
-
/**
|
|
278
|
-
* Like `traced`, but accumulates `llm.cost.*` from all descendant LLM spans
|
|
279
|
-
* and sets the rolled-up totals on the parent span when `fn` completes.
|
|
280
|
-
* Use on root spans (`cli.teach`, `cli.audit`) so Phoenix shows the full cost.
|
|
281
|
-
*/
|
|
282
|
-
export async function tracedWithCostRollup<T>(
|
|
283
|
-
name: string,
|
|
284
|
-
kind: TraceKind,
|
|
285
|
-
attributes: TraceAttributes | undefined,
|
|
286
|
-
fn: (span: Span) => Promise<T> | T,
|
|
287
|
-
): Promise<T> {
|
|
288
|
-
const acc: CostAccumulator = {
|
|
289
|
-
inputTokens: 0,
|
|
290
|
-
outputTokens: 0,
|
|
291
|
-
cacheReadTokens: 0,
|
|
292
|
-
cacheWriteTokens: 0,
|
|
293
|
-
uncachedInputCost: 0,
|
|
294
|
-
cacheReadCost: 0,
|
|
295
|
-
cacheWriteCost: 0,
|
|
296
|
-
completionCost: 0,
|
|
297
|
-
};
|
|
298
|
-
|
|
299
|
-
const applyCostRollup = (span: Span): void => {
|
|
300
|
-
const promptCost = acc.uncachedInputCost + acc.cacheReadCost + acc.cacheWriteCost;
|
|
301
|
-
const totalCost = promptCost + acc.completionCost;
|
|
302
|
-
if (totalCost === 0 && acc.inputTokens === 0 && acc.outputTokens === 0) return;
|
|
303
|
-
setSpanAttributes(span, {
|
|
304
|
-
[SemanticConventions.LLM_TOKEN_COUNT_PROMPT]: acc.inputTokens,
|
|
305
|
-
[SemanticConventions.LLM_TOKEN_COUNT_COMPLETION]: acc.outputTokens,
|
|
306
|
-
[SemanticConventions.LLM_TOKEN_COUNT_TOTAL]: acc.inputTokens + acc.outputTokens,
|
|
307
|
-
[SemanticConventions.LLM_TOKEN_COUNT_PROMPT_DETAILS_CACHE_READ]: acc.cacheReadTokens,
|
|
308
|
-
[SemanticConventions.LLM_TOKEN_COUNT_PROMPT_DETAILS_CACHE_WRITE]: acc.cacheWriteTokens,
|
|
309
|
-
[SemanticConventions.LLM_COST_PROMPT]: promptCost,
|
|
310
|
-
[SemanticConventions.LLM_COST_COMPLETION]: acc.completionCost,
|
|
311
|
-
[SemanticConventions.LLM_COST_TOTAL]: totalCost,
|
|
312
|
-
[SemanticConventions.LLM_COST_PROMPT_DETAILS_CACHE_READ]: acc.cacheReadCost,
|
|
313
|
-
[SemanticConventions.LLM_COST_PROMPT_DETAILS_CACHE_WRITE]: acc.cacheWriteCost,
|
|
314
|
-
[SemanticConventions.LLM_COST_INPUT]: acc.uncachedInputCost,
|
|
315
|
-
'imprint.llm.cost_estimated': true,
|
|
316
|
-
});
|
|
317
|
-
};
|
|
318
|
-
|
|
319
|
-
return costAccumulatorStorage.run(acc, () =>
|
|
320
|
-
traced(name, kind, attributes, async (span) => {
|
|
321
|
-
try {
|
|
322
|
-
return await fn(span);
|
|
323
|
-
} finally {
|
|
324
|
-
applyCostRollup(span);
|
|
325
|
-
}
|
|
326
|
-
}),
|
|
327
|
-
);
|
|
328
|
-
}
|
|
329
|
-
|
|
330
244
|
export function startTraceSpan(
|
|
331
245
|
name: string,
|
|
332
246
|
kind: TraceKind,
|
|
@@ -357,7 +271,7 @@ export function endTraceSpan(span: Span | null | undefined, err?: unknown): void
|
|
|
357
271
|
span.end();
|
|
358
272
|
}
|
|
359
273
|
|
|
360
|
-
|
|
274
|
+
interface LlmSpanAttributeOptions {
|
|
361
275
|
provider: string;
|
|
362
276
|
model?: string;
|
|
363
277
|
inputTokens?: number | null;
|
|
@@ -375,21 +289,13 @@ export function llmSpanAttributes(opts: {
|
|
|
375
289
|
inputMimeType?: string;
|
|
376
290
|
outputMimeType?: string;
|
|
377
291
|
invocationParameters?: Record<string, unknown>;
|
|
378
|
-
}
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
export function llmSpanAttributes(opts: LlmSpanAttributeOptions): Attributes {
|
|
379
295
|
const prompt = opts.inputTokens ?? undefined;
|
|
380
296
|
const completion = opts.outputTokens ?? undefined;
|
|
381
|
-
const
|
|
382
|
-
const
|
|
383
|
-
costRates && (prompt !== undefined || completion !== undefined)
|
|
384
|
-
? llmCostAttributes({
|
|
385
|
-
inputTokens: prompt,
|
|
386
|
-
outputTokens: completion,
|
|
387
|
-
cacheReadTokens: opts.cacheReadTokens ?? undefined,
|
|
388
|
-
cacheWriteTokens: opts.cacheWriteTokens ?? undefined,
|
|
389
|
-
inputUsdPer1M: costRates.inputUsdPer1M,
|
|
390
|
-
outputUsdPer1M: costRates.outputUsdPer1M,
|
|
391
|
-
})
|
|
392
|
-
: {};
|
|
297
|
+
const cacheRead = opts.cacheReadTokens ?? undefined;
|
|
298
|
+
const cacheWrite = opts.cacheWriteTokens ?? undefined;
|
|
393
299
|
return {
|
|
394
300
|
...getLLMAttributes({
|
|
395
301
|
provider: openInferenceProvider(opts.provider),
|
|
@@ -416,7 +322,12 @@ export function llmSpanAttributes(opts: {
|
|
|
416
322
|
...(opts.outputValue
|
|
417
323
|
? traceInputOutputAttributes('output', opts.outputValue, opts.outputMimeType ?? MimeType.TEXT)
|
|
418
324
|
: {}),
|
|
419
|
-
...
|
|
325
|
+
...(cacheRead !== undefined
|
|
326
|
+
? { [SemanticConventions.LLM_TOKEN_COUNT_PROMPT_DETAILS_CACHE_READ]: cacheRead }
|
|
327
|
+
: {}),
|
|
328
|
+
...(cacheWrite !== undefined
|
|
329
|
+
? { [SemanticConventions.LLM_TOKEN_COUNT_PROMPT_DETAILS_CACHE_WRITE]: cacheWrite }
|
|
330
|
+
: {}),
|
|
420
331
|
...(opts.stopReason ? { [SemanticConventions.LLM_FINISH_REASON]: opts.stopReason } : {}),
|
|
421
332
|
'imprint.llm.provider': opts.provider,
|
|
422
333
|
...(opts.tokenCountsEstimated !== undefined
|
|
@@ -426,15 +337,34 @@ export function llmSpanAttributes(opts: {
|
|
|
426
337
|
...(opts.outputTokenSource
|
|
427
338
|
? { 'imprint.llm.output_tokens_source': opts.outputTokenSource }
|
|
428
339
|
: {}),
|
|
429
|
-
...(costRates
|
|
430
|
-
? {
|
|
431
|
-
'imprint.llm.cost.input_usd_per_1m': costRates.inputUsdPer1M,
|
|
432
|
-
'imprint.llm.cost.output_usd_per_1m': costRates.outputUsdPer1M,
|
|
433
|
-
}
|
|
434
|
-
: {}),
|
|
435
340
|
};
|
|
436
341
|
}
|
|
437
342
|
|
|
343
|
+
/**
|
|
344
|
+
* Record aggregate token usage reported by an external agent CLI without
|
|
345
|
+
* misclassifying the surrounding tool-driving workflow as one LLM invocation.
|
|
346
|
+
* The zero-duration child is deliberately marked as an aggregate usage carrier:
|
|
347
|
+
* Phoenix can price it, while workflow latency remains on the parent AGENT span.
|
|
348
|
+
*/
|
|
349
|
+
export function recordLlmUsageSpan(
|
|
350
|
+
name: string,
|
|
351
|
+
opts: LlmSpanAttributeOptions,
|
|
352
|
+
attributes: TraceAttributes = {},
|
|
353
|
+
): void {
|
|
354
|
+
const hasUsage =
|
|
355
|
+
(opts.inputTokens ?? 0) > 0 ||
|
|
356
|
+
(opts.outputTokens ?? 0) > 0 ||
|
|
357
|
+
(opts.cacheReadTokens ?? 0) > 0 ||
|
|
358
|
+
(opts.cacheWriteTokens ?? 0) > 0;
|
|
359
|
+
if (!hasUsage) return;
|
|
360
|
+
const span = startTraceSpan(name, 'LLM', {
|
|
361
|
+
...attributes,
|
|
362
|
+
'imprint.llm.usage_aggregate': true,
|
|
363
|
+
...llmSpanAttributes(opts),
|
|
364
|
+
});
|
|
365
|
+
endTraceSpan(span);
|
|
366
|
+
}
|
|
367
|
+
|
|
438
368
|
export function traceLlmMessages(messages: TraceLlmMessage[]): TraceLlmMessage[] {
|
|
439
369
|
return messages.map((message) => ({
|
|
440
370
|
...message,
|
|
@@ -539,114 +469,6 @@ function stringifyTraceValue(value: unknown): string {
|
|
|
539
469
|
}
|
|
540
470
|
}
|
|
541
471
|
|
|
542
|
-
function llmCostAttributes(opts: {
|
|
543
|
-
inputTokens?: number;
|
|
544
|
-
outputTokens?: number;
|
|
545
|
-
cacheReadTokens?: number;
|
|
546
|
-
cacheWriteTokens?: number;
|
|
547
|
-
inputUsdPer1M: number;
|
|
548
|
-
outputUsdPer1M: number;
|
|
549
|
-
}): Attributes {
|
|
550
|
-
const cacheRead = opts.cacheReadTokens ?? 0;
|
|
551
|
-
const cacheWrite = opts.cacheWriteTokens ?? 0;
|
|
552
|
-
const hasCacheBreakdown = cacheRead > 0 || cacheWrite > 0;
|
|
553
|
-
const uncachedInput =
|
|
554
|
-
opts.inputTokens === undefined
|
|
555
|
-
? undefined
|
|
556
|
-
: hasCacheBreakdown
|
|
557
|
-
? Math.max(0, opts.inputTokens - cacheRead - cacheWrite)
|
|
558
|
-
: opts.inputTokens;
|
|
559
|
-
|
|
560
|
-
let uncachedInputCost: number | undefined;
|
|
561
|
-
let cacheReadCost = 0;
|
|
562
|
-
let cacheWriteCost = 0;
|
|
563
|
-
if (uncachedInput !== undefined) {
|
|
564
|
-
if (hasCacheBreakdown) {
|
|
565
|
-
uncachedInputCost = (uncachedInput / 1_000_000) * opts.inputUsdPer1M;
|
|
566
|
-
cacheReadCost = (cacheRead / 1_000_000) * opts.inputUsdPer1M * CACHE_READ_MULTIPLIER;
|
|
567
|
-
cacheWriteCost = (cacheWrite / 1_000_000) * opts.inputUsdPer1M * CACHE_WRITE_MULTIPLIER;
|
|
568
|
-
} else {
|
|
569
|
-
uncachedInputCost = (uncachedInput / 1_000_000) * opts.inputUsdPer1M;
|
|
570
|
-
}
|
|
571
|
-
}
|
|
572
|
-
|
|
573
|
-
const prompt =
|
|
574
|
-
uncachedInputCost === undefined
|
|
575
|
-
? undefined
|
|
576
|
-
: uncachedInputCost + cacheReadCost + cacheWriteCost;
|
|
577
|
-
const completion =
|
|
578
|
-
opts.outputTokens === undefined
|
|
579
|
-
? undefined
|
|
580
|
-
: (opts.outputTokens / 1_000_000) * opts.outputUsdPer1M;
|
|
581
|
-
const total = (prompt ?? 0) + (completion ?? 0);
|
|
582
|
-
|
|
583
|
-
// Roll up into the nearest ancestor tracedWithCostRollup, if any.
|
|
584
|
-
const acc = getActiveCostAccumulator();
|
|
585
|
-
if (acc) {
|
|
586
|
-
acc.inputTokens += opts.inputTokens ?? 0;
|
|
587
|
-
acc.outputTokens += opts.outputTokens ?? 0;
|
|
588
|
-
acc.cacheReadTokens += cacheRead;
|
|
589
|
-
acc.cacheWriteTokens += cacheWrite;
|
|
590
|
-
acc.uncachedInputCost += uncachedInputCost ?? 0;
|
|
591
|
-
acc.cacheReadCost += cacheReadCost;
|
|
592
|
-
acc.cacheWriteCost += cacheWriteCost;
|
|
593
|
-
acc.completionCost += completion ?? 0;
|
|
594
|
-
}
|
|
595
|
-
|
|
596
|
-
return {
|
|
597
|
-
...(prompt !== undefined ? { [SemanticConventions.LLM_COST_PROMPT]: prompt } : {}),
|
|
598
|
-
...(completion !== undefined ? { [SemanticConventions.LLM_COST_COMPLETION]: completion } : {}),
|
|
599
|
-
[SemanticConventions.LLM_COST_TOTAL]: total,
|
|
600
|
-
...(hasCacheBreakdown
|
|
601
|
-
? {
|
|
602
|
-
[SemanticConventions.LLM_COST_PROMPT_DETAILS_CACHE_READ]: cacheReadCost,
|
|
603
|
-
[SemanticConventions.LLM_COST_PROMPT_DETAILS_CACHE_WRITE]: cacheWriteCost,
|
|
604
|
-
[SemanticConventions.LLM_COST_INPUT]: uncachedInputCost,
|
|
605
|
-
[SemanticConventions.LLM_TOKEN_COUNT_PROMPT_DETAILS_CACHE_READ]: cacheRead,
|
|
606
|
-
[SemanticConventions.LLM_TOKEN_COUNT_PROMPT_DETAILS_CACHE_WRITE]: cacheWrite,
|
|
607
|
-
}
|
|
608
|
-
: {}),
|
|
609
|
-
'imprint.llm.cost_estimated': true,
|
|
610
|
-
};
|
|
611
|
-
}
|
|
612
|
-
|
|
613
|
-
function rateEnvNames(
|
|
614
|
-
providerName: string,
|
|
615
|
-
modelName: string | undefined,
|
|
616
|
-
side: 'INPUT' | 'OUTPUT',
|
|
617
|
-
): string[] {
|
|
618
|
-
const providerKey = envKey(providerName);
|
|
619
|
-
const modelKey = modelName ? envKey(modelName) : undefined;
|
|
620
|
-
const aliases = side === 'INPUT' ? ['INPUT', 'PROMPT'] : ['OUTPUT', 'COMPLETION'];
|
|
621
|
-
const names: string[] = [];
|
|
622
|
-
for (const alias of aliases) {
|
|
623
|
-
if (providerKey && modelKey) {
|
|
624
|
-
names.push(`IMPRINT_TRACE_COST_${providerKey}_${modelKey}_${alias}_USD_PER_1M`);
|
|
625
|
-
}
|
|
626
|
-
if (modelKey) names.push(`IMPRINT_TRACE_COST_${modelKey}_${alias}_USD_PER_1M`);
|
|
627
|
-
if (providerKey) names.push(`IMPRINT_TRACE_COST_${providerKey}_${alias}_USD_PER_1M`);
|
|
628
|
-
names.push(`IMPRINT_TRACE_${alias}_USD_PER_1M`);
|
|
629
|
-
}
|
|
630
|
-
return names;
|
|
631
|
-
}
|
|
632
|
-
|
|
633
|
-
function envKey(value: string): string {
|
|
634
|
-
return value
|
|
635
|
-
.toUpperCase()
|
|
636
|
-
.replace(/[^A-Z0-9]+/g, '_')
|
|
637
|
-
.replace(/^_+|_+$/g, '');
|
|
638
|
-
}
|
|
639
|
-
|
|
640
|
-
function envNumber(names: string[]): number | null {
|
|
641
|
-
for (const name of names) {
|
|
642
|
-
const raw = process.env[name];
|
|
643
|
-
if (raw === undefined || raw === '') continue;
|
|
644
|
-
const parsed = Number(raw);
|
|
645
|
-
if (Number.isFinite(parsed) && parsed >= 0) return parsed;
|
|
646
|
-
}
|
|
647
|
-
return null;
|
|
648
|
-
}
|
|
649
|
-
|
|
650
472
|
function isTruthy(value: string | undefined): boolean {
|
|
651
473
|
if (!value) return false;
|
|
652
474
|
return !['0', 'false', 'no', 'off'].includes(value.toLowerCase());
|