@jeffreyjyz/reqshape 0.0.0-stage → 0.1.1
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/LICENSE +18 -0
- package/README.md +149 -2
- package/package.json +31 -5
- package/src/cli/flags.ts +108 -0
- package/src/cli/options.ts +55 -0
- package/src/cli/parse.ts +101 -0
- package/src/cli/run.ts +142 -0
- package/src/constants/ansi.ts +4 -0
- package/src/constants/asks.ts +14 -0
- package/src/constants/keywords.ts +91 -0
- package/src/constants/layout.ts +6 -0
- package/src/constants/providers.ts +2 -0
- package/src/index.ts +11 -0
- package/src/market/entries.ts +89 -0
- package/src/market/project.ts +61 -0
- package/src/market/rates.ts +103 -0
- package/src/market/sources.ts +62 -0
- package/src/measure/asks.ts +83 -0
- package/src/measure/filter.ts +116 -0
- package/src/measure/profile.ts +114 -0
- package/src/measure/rows.ts +125 -0
- package/src/measure/sides.ts +28 -0
- package/src/measure/stats.ts +47 -0
- package/src/measure/store.ts +93 -0
- package/src/types.ts +115 -0
- package/src/view/format.ts +66 -0
- package/src/view/json.ts +51 -0
- package/src/view/render.ts +96 -0
- package/src/view/sections.ts +119 -0
- package/src/view/table.ts +61 -0
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
import type { Projection } from "~/market/project.ts";
|
|
2
|
+
import type { AccountSummary } from "~/market/sources.ts";
|
|
3
|
+
import type { DropCount, Shape } from "~/types.ts";
|
|
4
|
+
import { bold, dim, fmtCount, fmtInt } from "./format.ts";
|
|
5
|
+
import {
|
|
6
|
+
block,
|
|
7
|
+
dropLines,
|
|
8
|
+
measured,
|
|
9
|
+
models,
|
|
10
|
+
perReq,
|
|
11
|
+
position,
|
|
12
|
+
sides,
|
|
13
|
+
} from "./sections.ts";
|
|
14
|
+
import { tableBody, tableHeader, tableRule } from "./table.ts";
|
|
15
|
+
|
|
16
|
+
export interface Report {
|
|
17
|
+
shape: Shape;
|
|
18
|
+
projections: Projection[];
|
|
19
|
+
/** Rows the catalogue held before --model / --limit narrowed them. */
|
|
20
|
+
total: number;
|
|
21
|
+
drops: DropCount[];
|
|
22
|
+
weight: "turn" | "session";
|
|
23
|
+
account: AccountSummary | null;
|
|
24
|
+
layout: string;
|
|
25
|
+
explain: boolean;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
export function renderText(report: Report): string {
|
|
29
|
+
const { shape, projections, weight } = report;
|
|
30
|
+
const out: string[] = [];
|
|
31
|
+
|
|
32
|
+
out.push(
|
|
33
|
+
`${bold("reqshape")} ${fmtCount(shape.reqs)} reqs · ${
|
|
34
|
+
weight === "session"
|
|
35
|
+
? "each conversation one vote"
|
|
36
|
+
: "every req weighted equally"
|
|
37
|
+
}`,
|
|
38
|
+
dim(
|
|
39
|
+
`measured from the opencode ${report.layout} store · priced by mpc`,
|
|
40
|
+
),
|
|
41
|
+
"",
|
|
42
|
+
...block("MEASURED", measured(shape)),
|
|
43
|
+
...block("DROPPED", dropLines(report.drops, report.explain)),
|
|
44
|
+
"",
|
|
45
|
+
...block("PER REQ", perReq(shape, weight)),
|
|
46
|
+
"",
|
|
47
|
+
...block("PER SIDE", sides(shape)),
|
|
48
|
+
"",
|
|
49
|
+
...block("POSITION", position(shape)),
|
|
50
|
+
"",
|
|
51
|
+
...block("MODELS", models(shape)),
|
|
52
|
+
"",
|
|
53
|
+
);
|
|
54
|
+
|
|
55
|
+
if (projections.length === 0) {
|
|
56
|
+
out.push("no model rows to price");
|
|
57
|
+
} else {
|
|
58
|
+
out.push(
|
|
59
|
+
...block("PROJECTED", [
|
|
60
|
+
dim("what the plan's allowance buys at the measured shape"),
|
|
61
|
+
]),
|
|
62
|
+
"",
|
|
63
|
+
tableHeader(),
|
|
64
|
+
tableRule(),
|
|
65
|
+
...tableBody(projections),
|
|
66
|
+
"",
|
|
67
|
+
);
|
|
68
|
+
if (report.total > projections.length) {
|
|
69
|
+
out.push(
|
|
70
|
+
dim(
|
|
71
|
+
`showing ${fmtInt(projections.length)} of ${fmtInt(report.total)} model-plans`,
|
|
72
|
+
),
|
|
73
|
+
);
|
|
74
|
+
}
|
|
75
|
+
if (projections.some((projection) => projection.cacheAtInput)) {
|
|
76
|
+
out.push(
|
|
77
|
+
dim(
|
|
78
|
+
"* publishes no cache-read rate, so the context is billed at the input rate",
|
|
79
|
+
),
|
|
80
|
+
);
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
if (report.account) {
|
|
85
|
+
out.push(
|
|
86
|
+
dim(
|
|
87
|
+
`CC account ${fmtInt(report.account.requests)} reqs this period · ${report.account.plan} · ends ${report.account.periodEnd}`,
|
|
88
|
+
),
|
|
89
|
+
);
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
return `${out
|
|
93
|
+
.join("\n")
|
|
94
|
+
.replace(/\n{3,}/g, "\n\n")
|
|
95
|
+
.trimEnd()}\n`;
|
|
96
|
+
}
|
|
@@ -0,0 +1,119 @@
|
|
|
1
|
+
import { LABEL } from "~/constants/layout.ts";
|
|
2
|
+
import type { DropCount, Shape, Side } from "~/types.ts";
|
|
3
|
+
import {
|
|
4
|
+
bold,
|
|
5
|
+
dim,
|
|
6
|
+
fmtCount,
|
|
7
|
+
fmtDate,
|
|
8
|
+
fmtInt,
|
|
9
|
+
fmtPair,
|
|
10
|
+
pad,
|
|
11
|
+
prettyModel,
|
|
12
|
+
} from "./format.ts";
|
|
13
|
+
|
|
14
|
+
export function block(label: string, lines: string[]): string[] {
|
|
15
|
+
return lines.map((line, index) =>
|
|
16
|
+
index === 0
|
|
17
|
+
? `${bold(pad(label, LABEL))}${line}`
|
|
18
|
+
: `${" ".repeat(LABEL)}${line}`,
|
|
19
|
+
);
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export function dropLines(drops: DropCount[], explain: boolean): string[] {
|
|
23
|
+
if (drops.length === 0) return ["nothing"];
|
|
24
|
+
const lines = drops.map((drop) => `${drop.reason} ${fmtInt(drop.asks)}`);
|
|
25
|
+
if (explain) return lines;
|
|
26
|
+
return [lines.slice(0, 6).join(dim(" · "))];
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
export function measured(shape: Shape): string[] {
|
|
30
|
+
return [
|
|
31
|
+
[
|
|
32
|
+
`${bold(fmtInt(shape.asks))} asks`,
|
|
33
|
+
`${fmtInt(shape.sessions)} sessions`,
|
|
34
|
+
`${fmtInt(shape.projects)} projects`,
|
|
35
|
+
`${fmtDate(shape.first)} → ${fmtDate(shape.last)}`,
|
|
36
|
+
].join(dim(" · ")),
|
|
37
|
+
];
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
export function perReq(shape: Shape, weight: "turn" | "session"): string[] {
|
|
41
|
+
const { stats, perReq: req, perSession: session } = shape;
|
|
42
|
+
return [
|
|
43
|
+
[
|
|
44
|
+
`input ${bold(fmtCount(req.input))}`,
|
|
45
|
+
`output ${bold(fmtCount(req.output))}`,
|
|
46
|
+
`reasoning ${fmtCount(req.reasoning)}`,
|
|
47
|
+
`cache read ${bold(fmtCount(req.cacheRead))}`,
|
|
48
|
+
`cache write ${fmtCount(req.cacheWrite)}`,
|
|
49
|
+
].join(dim(" · ")),
|
|
50
|
+
dim(
|
|
51
|
+
[
|
|
52
|
+
`p10/p90 input ${fmtPair(stats.input.p10, stats.input.p90)}`,
|
|
53
|
+
`output ${fmtPair(stats.output.p10, stats.output.p90)}`,
|
|
54
|
+
`cache read ${fmtPair(stats.cacheRead.p10, stats.cacheRead.p90)}`,
|
|
55
|
+
].join(" · "),
|
|
56
|
+
),
|
|
57
|
+
dim(
|
|
58
|
+
weight === "session"
|
|
59
|
+
? "one vote per conversation, which is what the projection prices"
|
|
60
|
+
: `one vote per conversation instead: cache read ${fmtCount(session.cacheRead)} · input ${fmtCount(session.input)} · output ${fmtCount(session.output)}`,
|
|
61
|
+
),
|
|
62
|
+
];
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
/** What each plan's own traffic looks like, which is what mpc prices per side. */
|
|
66
|
+
export function sides(shape: Shape): string[] {
|
|
67
|
+
const names: Record<Side, string> = {
|
|
68
|
+
oc: "OpenCode Go",
|
|
69
|
+
cc: "CommandCode",
|
|
70
|
+
};
|
|
71
|
+
const lines = (["oc", "cc"] as const).flatMap((side) => {
|
|
72
|
+
const measured = shape.sides[side];
|
|
73
|
+
if (!measured) return [];
|
|
74
|
+
const { profile, reqs } = measured;
|
|
75
|
+
return [
|
|
76
|
+
[
|
|
77
|
+
`${pad(names[side], 12)}`,
|
|
78
|
+
`input ${pad(fmtCount(profile.input), 8, "right")}`,
|
|
79
|
+
`output ${pad(fmtCount(profile.output), 7, "right")}`,
|
|
80
|
+
`cache read ${pad(fmtCount(profile.cacheRead), 9, "right")}`,
|
|
81
|
+
dim(`${fmtInt(reqs)} reqs`),
|
|
82
|
+
].join(" "),
|
|
83
|
+
];
|
|
84
|
+
});
|
|
85
|
+
if (lines.length === 0) return [];
|
|
86
|
+
return [dim("each side priced on its own traffic"), ...lines];
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
export function position(shape: Shape): string[] {
|
|
90
|
+
if (shape.buckets.length === 0) return [];
|
|
91
|
+
const rows = shape.buckets.map((bucket) =>
|
|
92
|
+
[
|
|
93
|
+
`pos ${pad(bucket.label, 7)}`,
|
|
94
|
+
`cache read ${pad(fmtCount(bucket.cacheRead), 8, "right")}`,
|
|
95
|
+
`input ${pad(fmtCount(bucket.input), 7, "right")}`,
|
|
96
|
+
`output ${pad(fmtCount(bucket.output), 6, "right")}`,
|
|
97
|
+
dim(`${fmtInt(bucket.reqs)} reqs`),
|
|
98
|
+
].join(" "),
|
|
99
|
+
);
|
|
100
|
+
return [
|
|
101
|
+
dim(
|
|
102
|
+
"how far into its session the req sat — every call re-reads the context",
|
|
103
|
+
),
|
|
104
|
+
...rows,
|
|
105
|
+
];
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
export function models(shape: Shape): string[] {
|
|
109
|
+
if (shape.models.length === 0) return [];
|
|
110
|
+
const rest = shape.models.length - 6;
|
|
111
|
+
const line = shape.models
|
|
112
|
+
.slice(0, 6)
|
|
113
|
+
.map(
|
|
114
|
+
(model) =>
|
|
115
|
+
`${prettyModel(model.name)} ${dim(fmtCount(model.reqs))}`,
|
|
116
|
+
)
|
|
117
|
+
.join(dim(" · "));
|
|
118
|
+
return [rest > 0 ? `${line}${dim(` …${rest} more`)}` : line];
|
|
119
|
+
}
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
import { CELL, MONEY, NAME, PLAN } from "~/constants/layout.ts";
|
|
2
|
+
import type { Projection } from "~/market/project.ts";
|
|
3
|
+
import { planLabel } from "~/market/rates.ts";
|
|
4
|
+
import { dim, fmtCount, fmtUsd, pad } from "./format.ts";
|
|
5
|
+
|
|
6
|
+
export function tableHeader(): string {
|
|
7
|
+
return [
|
|
8
|
+
pad("MODEL", NAME),
|
|
9
|
+
pad("PLAN", PLAN),
|
|
10
|
+
pad("$/req", MONEY, "right"),
|
|
11
|
+
pad("req/mo", CELL, "right"),
|
|
12
|
+
pad("req/5h", CELL, "right"),
|
|
13
|
+
pad("req/wk", CELL, "right"),
|
|
14
|
+
]
|
|
15
|
+
.join(" ")
|
|
16
|
+
.trimEnd();
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export function tableRule(): string {
|
|
20
|
+
return dim(
|
|
21
|
+
[
|
|
22
|
+
"─".repeat(NAME),
|
|
23
|
+
"─".repeat(PLAN),
|
|
24
|
+
"─".repeat(MONEY),
|
|
25
|
+
"─".repeat(CELL),
|
|
26
|
+
"─".repeat(CELL),
|
|
27
|
+
"─".repeat(CELL),
|
|
28
|
+
].join(" "),
|
|
29
|
+
);
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
export function tableBody(projections: Projection[]): string[] {
|
|
33
|
+
return projections.map((projection) => {
|
|
34
|
+
// Only the cache-read case is worth flagging: it is 300K tokens a request,
|
|
35
|
+
// where a cache write is a rounding error.
|
|
36
|
+
const mark = projection.cacheAtInput ? " *" : "";
|
|
37
|
+
return [
|
|
38
|
+
pad(projection.entry.model, NAME),
|
|
39
|
+
pad(planLabel(projection.entry), PLAN),
|
|
40
|
+
pad(fmtUsd(projection.costPerReq), MONEY, "right"),
|
|
41
|
+
pad(fmtCount(projection.requestsPerMonth), CELL, "right"),
|
|
42
|
+
pad(
|
|
43
|
+
projection.requestsPerFiveHour === null
|
|
44
|
+
? "—"
|
|
45
|
+
: fmtCount(projection.requestsPerFiveHour),
|
|
46
|
+
CELL,
|
|
47
|
+
"right",
|
|
48
|
+
),
|
|
49
|
+
pad(
|
|
50
|
+
projection.requestsPerWeek === null
|
|
51
|
+
? "—"
|
|
52
|
+
: fmtCount(projection.requestsPerWeek),
|
|
53
|
+
CELL,
|
|
54
|
+
"right",
|
|
55
|
+
),
|
|
56
|
+
]
|
|
57
|
+
.join(" ")
|
|
58
|
+
.trimEnd()
|
|
59
|
+
.concat(mark);
|
|
60
|
+
});
|
|
61
|
+
}
|