@trazum/cli 1.50.2 → 1.50.3
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/dist/gateway-server.d.ts +91 -0
- package/dist/gateway-server.d.ts.map +1 -0
- package/dist/gateway-server.js +290 -0
- package/dist/gateway-server.js.map +1 -0
- package/dist/i18n/en.d.ts.map +1 -1
- package/dist/i18n/en.js +33 -0
- package/dist/i18n/en.js.map +1 -1
- package/dist/i18n/es.d.ts.map +1 -1
- package/dist/i18n/es.js +35 -0
- package/dist/i18n/es.js.map +1 -1
- package/dist/i18n/types.d.ts +18 -0
- package/dist/i18n/types.d.ts.map +1 -1
- package/dist/index.js +116 -7
- package/dist/index.js.map +1 -1
- package/package.json +2 -2
- package/src/gateway-server.ts +350 -0
- package/src/i18n/en.ts +43 -0
- package/src/i18n/es.ts +45 -0
- package/src/i18n/types.ts +19 -0
- package/src/index.ts +155 -5
package/dist/index.js
CHANGED
|
@@ -4,7 +4,7 @@ import { open, readdir, readFile, stat, writeFile } from 'node:fs/promises';
|
|
|
4
4
|
import { dirname, join, resolve as resolvePath } from 'node:path';
|
|
5
5
|
import { fileURLToPath } from 'node:url';
|
|
6
6
|
import { gunzipSync } from 'node:zlib';
|
|
7
|
-
import { applyRewrites, BASELINE_FILENAME, BASELINE_VERSION, breaches, cacheableMinimum, analyzeCachePrefix, billLevers, bucketedCacheEconomics, bucketedProfile, buildHistory, buildPlan, connectorFor, CONNECTORS, normalizeAnthropicUsage, normalizeOpenAIUsage, bucketsFromRecords, evaluateWatch, firedKey, pruneRecords, recordsFromBuckets, storeInventory, storedReportFrom, verifyPlan, cacheEconomics, cacheHitRate, contextPressure, comparePrompts, compareToBaseline, computeSavings, countTokensAnthropic, DEFAULT_USAGE, budgetPositions, conform, detectFromSource, matchLocale, parsePlanDocument, waiverDay, waiverHistory, proposeInit, MIN_RATE_DAYS, parseConfig, coverageDrift, driversBetween, explainGateFailure, assignSources, fleetRollup, labelCoverage, measuredUsage, gateMargin, GATE_MARGIN_TIGHT, estimateTokens, evaluate, extractPrompts, findExamples, formatBaseline, formatSignedUsd, formatUsd, getMessages, getModel, hasMarker, LOCALES, MAX_BASELINE_BYTES, moneyIsComparable, mostSpecificMatch, nearestName, optimize, parseBaseline, PHRASE_LANGUAGES, plannedCalls, profilePrompt, profileToCsv, profileUsage, promptId, providerFromEnv, pruneExamples, refineWithLlm, rejectionText, reorderForCache, repriceProfile, reviewAgeDays, reviewExamples, RULES, sharedPrefixes, sharesOf, SOURCE_EXTENSIONS, suggestRewrites, toOtlpMetrics, toPromptfoo, TTL_1H_MS, UNLABELLED, withExactTokenCounts, } from '@trazum/core';
|
|
7
|
+
import { applyRewrites, BASELINE_FILENAME, BASELINE_VERSION, breaches, cacheableMinimum, analyzeCachePrefix, billLevers, bucketedCacheEconomics, bucketedProfile, buildHistory, buildPlan, connectorFor, CONNECTORS, normalizeAnthropicUsage, normalizeOpenAIUsage, bucketsFromRecords, evaluateWatch, firedKey, pruneRecords, recordsFromBuckets, storeInventory, storedReportFrom, verifyPlan, cacheEconomics, cacheHitRate, contextPressure, comparePrompts, compareToBaseline, computeSavings, countTokensAnthropic, DEFAULT_USAGE, budgetPositions, conform, FAILURE_POLICIES, detectFromSource, matchLocale, parsePlanDocument, waiverDay, waiverHistory, proposeInit, MIN_RATE_DAYS, parseConfig, coverageDrift, driversBetween, explainGateFailure, assignSources, fleetRollup, labelCoverage, measuredUsage, gateMargin, GATE_MARGIN_TIGHT, estimateTokens, evaluate, extractPrompts, findExamples, formatBaseline, formatSignedUsd, formatUsd, getMessages, getModel, hasMarker, LOCALES, MAX_BASELINE_BYTES, moneyIsComparable, mostSpecificMatch, nearestName, optimize, parseBaseline, PHRASE_LANGUAGES, plannedCalls, profilePrompt, profileToCsv, profileUsage, promptId, providerFromEnv, pruneExamples, refineWithLlm, rejectionText, reorderForCache, repriceProfile, reviewAgeDays, reviewExamples, RULES, sharedPrefixes, sharesOf, SOURCE_EXTENSIONS, suggestRewrites, toOtlpMetrics, toPromptfoo, TTL_1H_MS, UNLABELLED, withExactTokenCounts, } from '@trazum/core';
|
|
8
8
|
import { cacheDir, cacheStats, cachingProvider, clearCache } from './suggest-cache.js';
|
|
9
9
|
import { dayOf, formatGap, median, spanDays } from './time.js';
|
|
10
10
|
// Everything that reads the filesystem, on its own entry point so the web
|
|
@@ -15,6 +15,7 @@ import { fetchProviderUsage, findCredential } from './connect.js';
|
|
|
15
15
|
import { STORE_DIR, appendRecords, readStore, rewriteStore } from './store-fs.js';
|
|
16
16
|
import { WAIVER_LOG, appendWaiverUse, readWaiverLog } from './waiver-log.js';
|
|
17
17
|
import { DEFAULT_PORT, buildServer, listen } from './serve.js';
|
|
18
|
+
import { DEFAULT_GATEWAY_PORT, UPSTREAMS, buildGateway, listenGateway, } from './gateway-server.js';
|
|
18
19
|
import { WATCH_STATE_VERSION, checkWebhook, postWebhook, readWatchState, writeWatchState, } from './watch-run.js';
|
|
19
20
|
import { LOCALE_ENV_VARS, detectLocale, getCliMessages } from './i18n/index.js';
|
|
20
21
|
import { MAX_SUMMARY_CHARS, fitWithin, renderBlameMarkdown, renderCheckMarkdown, renderDiffMarkdown, renderRankMarkdown, renderProfileMarkdown, } from './markdown.js';
|
|
@@ -33,6 +34,7 @@ const c = {
|
|
|
33
34
|
const VALUE_FLAGS = new Set([
|
|
34
35
|
'against',
|
|
35
36
|
'contract',
|
|
37
|
+
'on-cannot-tell',
|
|
36
38
|
'from-log',
|
|
37
39
|
'min-usd',
|
|
38
40
|
'payload',
|
|
@@ -316,6 +318,7 @@ const COMMAND_FLAGS = {
|
|
|
316
318
|
init: ['dry-run', 'yes', 'json', 'pricing', 'pricing-live'],
|
|
317
319
|
conform: ['contract', 'json'],
|
|
318
320
|
feedback: [],
|
|
321
|
+
gateway: ['on-cannot-tell', 'port', 'socket', 'pricing', 'pricing-live'],
|
|
319
322
|
where: [],
|
|
320
323
|
rules: [],
|
|
321
324
|
blame: ['limit', 'model', 'calls', 'output-tokens', 'batch', 'prompt', 'markdown-out'],
|
|
@@ -1575,6 +1578,93 @@ function commandFeedback(t) {
|
|
|
1575
1578
|
console.log(` ${url}`);
|
|
1576
1579
|
console.log();
|
|
1577
1580
|
}
|
|
1581
|
+
/**
|
|
1582
|
+
* `trazum gateway <provider>` — in the path, and refusing rather than advising.
|
|
1583
|
+
*
|
|
1584
|
+
* The last thing this product could not do. `serve` answers a question an
|
|
1585
|
+
* implementation may ignore; a connector reports the runaway after it ran.
|
|
1586
|
+
* Standing between the caller and the provider fixes both — usage is measured
|
|
1587
|
+
* from the provider's own response as it comes back, and a refusal is a
|
|
1588
|
+
* refusal.
|
|
1589
|
+
*
|
|
1590
|
+
* **The failure policy is required.** `--on-cannot-tell fail-open` keeps the
|
|
1591
|
+
* product working and lets the bill run; `fail-closed` stops the bill and takes
|
|
1592
|
+
* the product down with it. Both are defensible and there is deliberately no
|
|
1593
|
+
* default: a proxy that picks silently has made the most consequential decision
|
|
1594
|
+
* in somebody's architecture on their behalf, at install time, without saying
|
|
1595
|
+
* so.
|
|
1596
|
+
*
|
|
1597
|
+
* **Substitution is off unless it is written down.** `spend.substitute` in the
|
|
1598
|
+
* config, with the operator's own reason, and every substituted call is marked
|
|
1599
|
+
* so no later report treats it as the call the caller made.
|
|
1600
|
+
*/
|
|
1601
|
+
async function commandGateway(args, config, configDir, pricing, t) {
|
|
1602
|
+
const provider = args.positional[0];
|
|
1603
|
+
if (provider === undefined || UPSTREAMS[provider] === undefined) {
|
|
1604
|
+
throw new Error(t.gateway.badProvider(provider ?? '', Object.keys(UPSTREAMS).join(', ')));
|
|
1605
|
+
}
|
|
1606
|
+
/**
|
|
1607
|
+
* No default, and the error says why rather than just what.
|
|
1608
|
+
*
|
|
1609
|
+
* The one flag in this product that refuses to guess on the reader's behalf,
|
|
1610
|
+
* because the two answers differ in which failure they accept and nobody but
|
|
1611
|
+
* the operator knows which their product can survive.
|
|
1612
|
+
*/
|
|
1613
|
+
const policyFlag = stringFlag(args, 'on-cannot-tell');
|
|
1614
|
+
if (policyFlag === undefined || !FAILURE_POLICIES.includes(policyFlag)) {
|
|
1615
|
+
throw new Error(t.gateway.needsPolicy(FAILURE_POLICIES.join(', ')));
|
|
1616
|
+
}
|
|
1617
|
+
const { resolved } = await readStore(configDir);
|
|
1618
|
+
const budget = budgetPositions(resolved.records, config.spend, { catalogue: pricing });
|
|
1619
|
+
const position = budget.positions[0] ?? null;
|
|
1620
|
+
/**
|
|
1621
|
+
* Read once at start, like `serve`'s.
|
|
1622
|
+
*
|
|
1623
|
+
* A file read in the request path would put Trazum's own latency between a
|
|
1624
|
+
* caller and their provider on every call, which is a cost this product
|
|
1625
|
+
* would otherwise be reporting on somebody else. The staleness is real, so a
|
|
1626
|
+
* refusal carries `asOfMs` and says what it rested on.
|
|
1627
|
+
*/
|
|
1628
|
+
const standing = position === null || position.coverage === 'none'
|
|
1629
|
+
? null
|
|
1630
|
+
: {
|
|
1631
|
+
limitUsd: position.limitUsd,
|
|
1632
|
+
consumedUsd: position.consumedUsd,
|
|
1633
|
+
provenance: 'measured',
|
|
1634
|
+
asOfMs: Date.now(),
|
|
1635
|
+
};
|
|
1636
|
+
const measured = { calls: 0, usd: 0 };
|
|
1637
|
+
const server = buildGateway({
|
|
1638
|
+
provider,
|
|
1639
|
+
catalogue: pricing,
|
|
1640
|
+
policy: {
|
|
1641
|
+
onCannotTell: policyFlag,
|
|
1642
|
+
...(config.spend?.substitute === undefined ? {} : { substitute: config.spend.substitute }),
|
|
1643
|
+
},
|
|
1644
|
+
standing: () => standing,
|
|
1645
|
+
record: (call) => {
|
|
1646
|
+
measured.calls += 1;
|
|
1647
|
+
console.error(c.dim(t.gateway.measured(call.model, call.label, call.inputTokens, call.outputTokens, call.substituted)));
|
|
1648
|
+
},
|
|
1649
|
+
note: (line) => {
|
|
1650
|
+
console.error(c.yellow(` ${line}`));
|
|
1651
|
+
},
|
|
1652
|
+
});
|
|
1653
|
+
const socket = stringFlag(args, 'socket');
|
|
1654
|
+
const portRaw = stringFlag(args, 'port');
|
|
1655
|
+
const port = portRaw === undefined ? DEFAULT_GATEWAY_PORT : Number(portRaw);
|
|
1656
|
+
if (socket === undefined && (!Number.isInteger(port) || port < 0 || port > 65_535)) {
|
|
1657
|
+
throw new Error(t.serve.badPort(String(portRaw)));
|
|
1658
|
+
}
|
|
1659
|
+
const where = await listenGateway(server, socket !== undefined ? { socket } : { port });
|
|
1660
|
+
console.log(c.bold(t.gateway.listening(where, provider)));
|
|
1661
|
+
console.log(` ${c.dim(wrap(t.gateway.pointYourSdk(where), 74, ' '))}`);
|
|
1662
|
+
console.log(` ${c.dim(wrap(t.gateway.credential(), 74, ' '))}`);
|
|
1663
|
+
console.log(` ${c.dim(wrap(t.gateway.neverSubstitutes(), 74, ' '))}`);
|
|
1664
|
+
console.log(` ${c.dim(wrap(standing === null ? t.gateway.noStanding() : t.gateway.standing(formatUsd(standing.consumedUsd), formatUsd(standing.limitUsd)), 74, ' '))}`);
|
|
1665
|
+
console.log(` ${c.dim(wrap(t.gateway.policy(policyFlag), 74, ' '))}`);
|
|
1666
|
+
console.log();
|
|
1667
|
+
}
|
|
1578
1668
|
function commandModels(t, pricing) {
|
|
1579
1669
|
const n = (value) => value.toLocaleString(t.numberLocale);
|
|
1580
1670
|
const col = t.models.columns;
|
|
@@ -2761,7 +2851,7 @@ async function commandConnect(args, pricing, t) {
|
|
|
2761
2851
|
* same action planned twice — and no series, however long, becomes a
|
|
2762
2852
|
* forecast.
|
|
2763
2853
|
*/
|
|
2764
|
-
async function commandHistory(args, config, pricing, t) {
|
|
2854
|
+
async function commandHistory(args, config, configDir, pricing, t) {
|
|
2765
2855
|
/**
|
|
2766
2856
|
* `--store` builds the series from measured spend already on disk.
|
|
2767
2857
|
*
|
|
@@ -2873,7 +2963,7 @@ async function commandHistory(args, config, pricing, t) {
|
|
|
2873
2963
|
* the waiver record belongs to the repository whose gates fired, and the
|
|
2874
2964
|
* stored reports may have come from anywhere.
|
|
2875
2965
|
*/
|
|
2876
|
-
const waivers = await readWaiverLog(
|
|
2966
|
+
const waivers = await readWaiverLog(configDir);
|
|
2877
2967
|
const waiverReport = waiverHistory(waivers.uses, config.waive ?? []);
|
|
2878
2968
|
const stamped = { ...history, unrecognizedFiles: unrecognized, waivers: waiverReport };
|
|
2879
2969
|
const n = (value) => value.toLocaleString(t.numberLocale);
|
|
@@ -3225,7 +3315,7 @@ async function commandPlan(args, pricing, t) {
|
|
|
3225
3315
|
if (outPath !== undefined)
|
|
3226
3316
|
console.log(c.dim(wrap(t.plan.wrote(outPath), 74, '')));
|
|
3227
3317
|
}
|
|
3228
|
-
async function commandProfile(args, config, pricing, t) {
|
|
3318
|
+
async function commandProfile(args, config, configDir, pricing, t) {
|
|
3229
3319
|
const path = args.positional[0];
|
|
3230
3320
|
if (path === undefined) {
|
|
3231
3321
|
console.log();
|
|
@@ -3984,7 +4074,7 @@ async function commandProfile(args, config, pricing, t) {
|
|
|
3984
4074
|
if (waiverUses.length === 0)
|
|
3985
4075
|
return;
|
|
3986
4076
|
for (const use of waiverUses) {
|
|
3987
|
-
const failed = await appendWaiverUse(
|
|
4077
|
+
const failed = await appendWaiverUse(configDir, use);
|
|
3988
4078
|
if (failed !== null) {
|
|
3989
4079
|
console.error(c.dim(t.profile.waiveNotRecorded(WAIVER_LOG, failed)));
|
|
3990
4080
|
return;
|
|
@@ -6431,6 +6521,22 @@ async function main() {
|
|
|
6431
6521
|
};
|
|
6432
6522
|
}
|
|
6433
6523
|
const { config } = loaded;
|
|
6524
|
+
/**
|
|
6525
|
+
* Where the waiver record lives: **beside the config that declared it**.
|
|
6526
|
+
*
|
|
6527
|
+
* It used to be the process's working directory, which is a different place
|
|
6528
|
+
* whenever somebody runs `trazum profile ../logs/x.jsonl --config ../repo/
|
|
6529
|
+
* trazum.config.json` — and that is not hypothetical. This repository's own
|
|
6530
|
+
* test suite did exactly that from `packages/cli`, so sixty records of a
|
|
6531
|
+
* fixture's decisions accumulated in a package directory and one of them was
|
|
6532
|
+
* committed to `main`, where it sat for two releases.
|
|
6533
|
+
*
|
|
6534
|
+
* A waiver is a decision a *repository* made. The record of using it belongs
|
|
6535
|
+
* with the file that made it, not with wherever the terminal happened to be.
|
|
6536
|
+
* No config means no waivers, so there is nothing to write and `.` is never
|
|
6537
|
+
* reached.
|
|
6538
|
+
*/
|
|
6539
|
+
const configDir = loaded.path === null ? '.' : dirname(loaded.path);
|
|
6434
6540
|
const pricing = await pricingFor(args, loaded, t);
|
|
6435
6541
|
// The config only gets to choose the locale when nothing more explicit did.
|
|
6436
6542
|
if (config.locale && !stringFlag(args, 'locale')) {
|
|
@@ -6448,7 +6554,7 @@ async function main() {
|
|
|
6448
6554
|
await commandBaseline(args, config, pricing, t, locale);
|
|
6449
6555
|
break;
|
|
6450
6556
|
case 'profile':
|
|
6451
|
-
await commandProfile(args, config, pricing, t);
|
|
6557
|
+
await commandProfile(args, config, configDir, pricing, t);
|
|
6452
6558
|
break;
|
|
6453
6559
|
case 'plan':
|
|
6454
6560
|
await commandPlan(args, pricing, t);
|
|
@@ -6457,7 +6563,7 @@ async function main() {
|
|
|
6457
6563
|
await commandVerify(args, pricing, t);
|
|
6458
6564
|
break;
|
|
6459
6565
|
case 'history':
|
|
6460
|
-
await commandHistory(args, config, pricing, t);
|
|
6566
|
+
await commandHistory(args, config, configDir, pricing, t);
|
|
6461
6567
|
break;
|
|
6462
6568
|
case 'connect':
|
|
6463
6569
|
await commandConnect(args, pricing, t);
|
|
@@ -6486,6 +6592,9 @@ async function main() {
|
|
|
6486
6592
|
case 'models':
|
|
6487
6593
|
commandModels(t, pricing);
|
|
6488
6594
|
break;
|
|
6595
|
+
case 'gateway':
|
|
6596
|
+
await commandGateway(args, config, configDir, pricing, t);
|
|
6597
|
+
break;
|
|
6489
6598
|
case 'feedback':
|
|
6490
6599
|
commandFeedback(t);
|
|
6491
6600
|
break;
|