@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/src/index.ts
CHANGED
|
@@ -39,6 +39,7 @@ import {
|
|
|
39
39
|
DEFAULT_USAGE,
|
|
40
40
|
budgetPositions,
|
|
41
41
|
conform,
|
|
42
|
+
FAILURE_POLICIES,
|
|
42
43
|
detectFromSource,
|
|
43
44
|
matchLocale,
|
|
44
45
|
parsePlanDocument,
|
|
@@ -133,6 +134,8 @@ import type {
|
|
|
133
134
|
import type {
|
|
134
135
|
BudgetReport,
|
|
135
136
|
ContractName,
|
|
137
|
+
FailurePolicy,
|
|
138
|
+
GatewayStanding,
|
|
136
139
|
UsageProfileReport,
|
|
137
140
|
WaiverUse,
|
|
138
141
|
InitDecline,
|
|
@@ -179,6 +182,12 @@ import { fetchProviderUsage, findCredential } from './connect.js';
|
|
|
179
182
|
import { STORE_DIR, appendRecords, readStore, rewriteStore } from './store-fs.js';
|
|
180
183
|
import { WAIVER_LOG, appendWaiverUse, readWaiverLog } from './waiver-log.js';
|
|
181
184
|
import { DEFAULT_PORT, buildServer, listen } from './serve.js';
|
|
185
|
+
import {
|
|
186
|
+
DEFAULT_GATEWAY_PORT,
|
|
187
|
+
UPSTREAMS,
|
|
188
|
+
buildGateway,
|
|
189
|
+
listenGateway,
|
|
190
|
+
} from './gateway-server.js';
|
|
182
191
|
import {
|
|
183
192
|
WATCH_STATE_VERSION,
|
|
184
193
|
checkWebhook,
|
|
@@ -233,6 +242,7 @@ interface Args {
|
|
|
233
242
|
const VALUE_FLAGS = new Set([
|
|
234
243
|
'against',
|
|
235
244
|
'contract',
|
|
245
|
+
'on-cannot-tell',
|
|
236
246
|
'from-log',
|
|
237
247
|
'min-usd',
|
|
238
248
|
'payload',
|
|
@@ -557,6 +567,7 @@ const COMMAND_FLAGS: Record<string, string[]> = {
|
|
|
557
567
|
init: ['dry-run', 'yes', 'json', 'pricing', 'pricing-live'],
|
|
558
568
|
conform: ['contract', 'json'],
|
|
559
569
|
feedback: [],
|
|
570
|
+
gateway: ['on-cannot-tell', 'port', 'socket', 'pricing', 'pricing-live'],
|
|
560
571
|
where: [],
|
|
561
572
|
rules: [],
|
|
562
573
|
blame: ['limit', 'model', 'calls', 'output-tokens', 'batch', 'prompt', 'markdown-out'],
|
|
@@ -2064,6 +2075,119 @@ function commandFeedback(t: CliMessages): void {
|
|
|
2064
2075
|
console.log();
|
|
2065
2076
|
}
|
|
2066
2077
|
|
|
2078
|
+
/**
|
|
2079
|
+
* `trazum gateway <provider>` — in the path, and refusing rather than advising.
|
|
2080
|
+
*
|
|
2081
|
+
* The last thing this product could not do. `serve` answers a question an
|
|
2082
|
+
* implementation may ignore; a connector reports the runaway after it ran.
|
|
2083
|
+
* Standing between the caller and the provider fixes both — usage is measured
|
|
2084
|
+
* from the provider's own response as it comes back, and a refusal is a
|
|
2085
|
+
* refusal.
|
|
2086
|
+
*
|
|
2087
|
+
* **The failure policy is required.** `--on-cannot-tell fail-open` keeps the
|
|
2088
|
+
* product working and lets the bill run; `fail-closed` stops the bill and takes
|
|
2089
|
+
* the product down with it. Both are defensible and there is deliberately no
|
|
2090
|
+
* default: a proxy that picks silently has made the most consequential decision
|
|
2091
|
+
* in somebody's architecture on their behalf, at install time, without saying
|
|
2092
|
+
* so.
|
|
2093
|
+
*
|
|
2094
|
+
* **Substitution is off unless it is written down.** `spend.substitute` in the
|
|
2095
|
+
* config, with the operator's own reason, and every substituted call is marked
|
|
2096
|
+
* so no later report treats it as the call the caller made.
|
|
2097
|
+
*/
|
|
2098
|
+
async function commandGateway(
|
|
2099
|
+
args: Args,
|
|
2100
|
+
config: TrazumConfig,
|
|
2101
|
+
configDir: string,
|
|
2102
|
+
pricing: PricingCatalogue,
|
|
2103
|
+
t: CliMessages,
|
|
2104
|
+
): Promise<void> {
|
|
2105
|
+
const provider = args.positional[0];
|
|
2106
|
+
if (provider === undefined || UPSTREAMS[provider] === undefined) {
|
|
2107
|
+
throw new Error(t.gateway.badProvider(provider ?? '', Object.keys(UPSTREAMS).join(', ')));
|
|
2108
|
+
}
|
|
2109
|
+
|
|
2110
|
+
/**
|
|
2111
|
+
* No default, and the error says why rather than just what.
|
|
2112
|
+
*
|
|
2113
|
+
* The one flag in this product that refuses to guess on the reader's behalf,
|
|
2114
|
+
* because the two answers differ in which failure they accept and nobody but
|
|
2115
|
+
* the operator knows which their product can survive.
|
|
2116
|
+
*/
|
|
2117
|
+
const policyFlag = stringFlag(args, 'on-cannot-tell');
|
|
2118
|
+
if (policyFlag === undefined || !FAILURE_POLICIES.includes(policyFlag as FailurePolicy)) {
|
|
2119
|
+
throw new Error(t.gateway.needsPolicy(FAILURE_POLICIES.join(', ')));
|
|
2120
|
+
}
|
|
2121
|
+
|
|
2122
|
+
const { resolved } = await readStore(configDir);
|
|
2123
|
+
const budget = budgetPositions(resolved.records, config.spend, { catalogue: pricing });
|
|
2124
|
+
const position = budget.positions[0] ?? null;
|
|
2125
|
+
|
|
2126
|
+
/**
|
|
2127
|
+
* Read once at start, like `serve`'s.
|
|
2128
|
+
*
|
|
2129
|
+
* A file read in the request path would put Trazum's own latency between a
|
|
2130
|
+
* caller and their provider on every call, which is a cost this product
|
|
2131
|
+
* would otherwise be reporting on somebody else. The staleness is real, so a
|
|
2132
|
+
* refusal carries `asOfMs` and says what it rested on.
|
|
2133
|
+
*/
|
|
2134
|
+
const standing: GatewayStanding | null =
|
|
2135
|
+
position === null || position.coverage === 'none'
|
|
2136
|
+
? null
|
|
2137
|
+
: {
|
|
2138
|
+
limitUsd: position.limitUsd,
|
|
2139
|
+
consumedUsd: position.consumedUsd,
|
|
2140
|
+
provenance: 'measured',
|
|
2141
|
+
asOfMs: Date.now(),
|
|
2142
|
+
};
|
|
2143
|
+
|
|
2144
|
+
const measured: { calls: number; usd: number } = { calls: 0, usd: 0 };
|
|
2145
|
+
const server = buildGateway({
|
|
2146
|
+
provider,
|
|
2147
|
+
catalogue: pricing,
|
|
2148
|
+
policy: {
|
|
2149
|
+
onCannotTell: policyFlag as FailurePolicy,
|
|
2150
|
+
...(config.spend?.substitute === undefined ? {} : { substitute: config.spend.substitute }),
|
|
2151
|
+
},
|
|
2152
|
+
standing: () => standing,
|
|
2153
|
+
record: (call) => {
|
|
2154
|
+
measured.calls += 1;
|
|
2155
|
+
console.error(
|
|
2156
|
+
c.dim(
|
|
2157
|
+
t.gateway.measured(
|
|
2158
|
+
call.model,
|
|
2159
|
+
call.label,
|
|
2160
|
+
call.inputTokens,
|
|
2161
|
+
call.outputTokens,
|
|
2162
|
+
call.substituted,
|
|
2163
|
+
),
|
|
2164
|
+
),
|
|
2165
|
+
);
|
|
2166
|
+
},
|
|
2167
|
+
note: (line) => {
|
|
2168
|
+
console.error(c.yellow(` ${line}`));
|
|
2169
|
+
},
|
|
2170
|
+
});
|
|
2171
|
+
|
|
2172
|
+
const socket = stringFlag(args, 'socket');
|
|
2173
|
+
const portRaw = stringFlag(args, 'port');
|
|
2174
|
+
const port = portRaw === undefined ? DEFAULT_GATEWAY_PORT : Number(portRaw);
|
|
2175
|
+
if (socket === undefined && (!Number.isInteger(port) || port < 0 || port > 65_535)) {
|
|
2176
|
+
throw new Error(t.serve.badPort(String(portRaw)));
|
|
2177
|
+
}
|
|
2178
|
+
|
|
2179
|
+
const where = await listenGateway(server, socket !== undefined ? { socket } : { port });
|
|
2180
|
+
console.log(c.bold(t.gateway.listening(where, provider)));
|
|
2181
|
+
console.log(` ${c.dim(wrap(t.gateway.pointYourSdk(where), 74, ' '))}`);
|
|
2182
|
+
console.log(` ${c.dim(wrap(t.gateway.credential(), 74, ' '))}`);
|
|
2183
|
+
console.log(` ${c.dim(wrap(t.gateway.neverSubstitutes(), 74, ' '))}`);
|
|
2184
|
+
console.log(
|
|
2185
|
+
` ${c.dim(wrap(standing === null ? t.gateway.noStanding() : t.gateway.standing(formatUsd(standing.consumedUsd), formatUsd(standing.limitUsd)), 74, ' '))}`,
|
|
2186
|
+
);
|
|
2187
|
+
console.log(` ${c.dim(wrap(t.gateway.policy(policyFlag), 74, ' '))}`);
|
|
2188
|
+
console.log();
|
|
2189
|
+
}
|
|
2190
|
+
|
|
2067
2191
|
function commandModels(t: CliMessages, pricing: PricingCatalogue): void {
|
|
2068
2192
|
const n = (value: number): string => value.toLocaleString(t.numberLocale);
|
|
2069
2193
|
const col = t.models.columns;
|
|
@@ -3640,6 +3764,7 @@ async function commandConnect(
|
|
|
3640
3764
|
async function commandHistory(
|
|
3641
3765
|
args: Args,
|
|
3642
3766
|
config: TrazumConfig,
|
|
3767
|
+
configDir: string,
|
|
3643
3768
|
pricing: PricingCatalogue,
|
|
3644
3769
|
t: CliMessages,
|
|
3645
3770
|
): Promise<void> {
|
|
@@ -3759,7 +3884,7 @@ async function commandHistory(
|
|
|
3759
3884
|
* the waiver record belongs to the repository whose gates fired, and the
|
|
3760
3885
|
* stored reports may have come from anywhere.
|
|
3761
3886
|
*/
|
|
3762
|
-
const waivers = await readWaiverLog(
|
|
3887
|
+
const waivers = await readWaiverLog(configDir);
|
|
3763
3888
|
const waiverReport = waiverHistory(waivers.uses, config.waive ?? []);
|
|
3764
3889
|
|
|
3765
3890
|
const stamped = { ...history, unrecognizedFiles: unrecognized, waivers: waiverReport };
|
|
@@ -4161,7 +4286,13 @@ async function commandPlan(
|
|
|
4161
4286
|
if (outPath !== undefined) console.log(c.dim(wrap(t.plan.wrote(outPath), 74, '')));
|
|
4162
4287
|
}
|
|
4163
4288
|
|
|
4164
|
-
async function commandProfile(
|
|
4289
|
+
async function commandProfile(
|
|
4290
|
+
args: Args,
|
|
4291
|
+
config: TrazumConfig,
|
|
4292
|
+
configDir: string,
|
|
4293
|
+
pricing: PricingCatalogue,
|
|
4294
|
+
t: CliMessages,
|
|
4295
|
+
): Promise<void> {
|
|
4165
4296
|
const path = args.positional[0];
|
|
4166
4297
|
if (path === undefined) {
|
|
4167
4298
|
console.log();
|
|
@@ -4985,7 +5116,7 @@ async function commandProfile(args: Args, config: TrazumConfig, pricing: Pricing
|
|
|
4985
5116
|
const recordWaiverUses = async (): Promise<void> => {
|
|
4986
5117
|
if (waiverUses.length === 0) return;
|
|
4987
5118
|
for (const use of waiverUses) {
|
|
4988
|
-
const failed = await appendWaiverUse(
|
|
5119
|
+
const failed = await appendWaiverUse(configDir, use);
|
|
4989
5120
|
if (failed !== null) {
|
|
4990
5121
|
console.error(c.dim(t.profile.waiveNotRecorded(WAIVER_LOG, failed)));
|
|
4991
5122
|
return;
|
|
@@ -8014,6 +8145,22 @@ async function main(): Promise<void> {
|
|
|
8014
8145
|
};
|
|
8015
8146
|
}
|
|
8016
8147
|
const { config } = loaded;
|
|
8148
|
+
/**
|
|
8149
|
+
* Where the waiver record lives: **beside the config that declared it**.
|
|
8150
|
+
*
|
|
8151
|
+
* It used to be the process's working directory, which is a different place
|
|
8152
|
+
* whenever somebody runs `trazum profile ../logs/x.jsonl --config ../repo/
|
|
8153
|
+
* trazum.config.json` — and that is not hypothetical. This repository's own
|
|
8154
|
+
* test suite did exactly that from `packages/cli`, so sixty records of a
|
|
8155
|
+
* fixture's decisions accumulated in a package directory and one of them was
|
|
8156
|
+
* committed to `main`, where it sat for two releases.
|
|
8157
|
+
*
|
|
8158
|
+
* A waiver is a decision a *repository* made. The record of using it belongs
|
|
8159
|
+
* with the file that made it, not with wherever the terminal happened to be.
|
|
8160
|
+
* No config means no waivers, so there is nothing to write and `.` is never
|
|
8161
|
+
* reached.
|
|
8162
|
+
*/
|
|
8163
|
+
const configDir = loaded.path === null ? '.' : dirname(loaded.path);
|
|
8017
8164
|
const pricing = await pricingFor(args, loaded, t);
|
|
8018
8165
|
|
|
8019
8166
|
// The config only gets to choose the locale when nothing more explicit did.
|
|
@@ -8033,7 +8180,7 @@ async function main(): Promise<void> {
|
|
|
8033
8180
|
await commandBaseline(args, config, pricing, t, locale);
|
|
8034
8181
|
break;
|
|
8035
8182
|
case 'profile':
|
|
8036
|
-
await commandProfile(args, config, pricing, t);
|
|
8183
|
+
await commandProfile(args, config, configDir, pricing, t);
|
|
8037
8184
|
break;
|
|
8038
8185
|
case 'plan':
|
|
8039
8186
|
await commandPlan(args, pricing, t);
|
|
@@ -8042,7 +8189,7 @@ async function main(): Promise<void> {
|
|
|
8042
8189
|
await commandVerify(args, pricing, t);
|
|
8043
8190
|
break;
|
|
8044
8191
|
case 'history':
|
|
8045
|
-
await commandHistory(args, config, pricing, t);
|
|
8192
|
+
await commandHistory(args, config, configDir, pricing, t);
|
|
8046
8193
|
break;
|
|
8047
8194
|
case 'connect':
|
|
8048
8195
|
await commandConnect(args, pricing, t);
|
|
@@ -8071,6 +8218,9 @@ async function main(): Promise<void> {
|
|
|
8071
8218
|
case 'models':
|
|
8072
8219
|
commandModels(t, pricing);
|
|
8073
8220
|
break;
|
|
8221
|
+
case 'gateway':
|
|
8222
|
+
await commandGateway(args, config, configDir, pricing, t);
|
|
8223
|
+
break;
|
|
8074
8224
|
case 'feedback':
|
|
8075
8225
|
commandFeedback(t);
|
|
8076
8226
|
break;
|