@garygentry/system1-core 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.
- package/LICENSE +21 -0
- package/README.md +13 -0
- package/dist/config/consent.d.ts +11 -0
- package/dist/config/consent.d.ts.map +1 -0
- package/dist/config/consent.js +33 -0
- package/dist/config/consent.js.map +1 -0
- package/dist/config/load.d.ts +64 -0
- package/dist/config/load.d.ts.map +1 -0
- package/dist/config/load.js +216 -0
- package/dist/config/load.js.map +1 -0
- package/dist/config/session.d.ts +31 -0
- package/dist/config/session.d.ts.map +1 -0
- package/dist/config/session.js +36 -0
- package/dist/config/session.js.map +1 -0
- package/dist/config.d.ts +28 -0
- package/dist/config.d.ts.map +1 -0
- package/dist/config.js +64 -0
- package/dist/config.js.map +1 -0
- package/dist/decide.d.ts +57 -0
- package/dist/decide.d.ts.map +1 -0
- package/dist/decide.js +87 -0
- package/dist/decide.js.map +1 -0
- package/dist/egress/exclude.d.ts +24 -0
- package/dist/egress/exclude.d.ts.map +1 -0
- package/dist/egress/exclude.js +60 -0
- package/dist/egress/exclude.js.map +1 -0
- package/dist/egress/scrub.d.ts +12 -0
- package/dist/egress/scrub.d.ts.map +1 -0
- package/dist/egress/scrub.js +84 -0
- package/dist/egress/scrub.js.map +1 -0
- package/dist/egress/size.d.ts +16 -0
- package/dist/egress/size.d.ts.map +1 -0
- package/dist/egress/size.js +25 -0
- package/dist/egress/size.js.map +1 -0
- package/dist/errors.d.ts +44 -0
- package/dist/errors.d.ts.map +1 -0
- package/dist/errors.js +23 -0
- package/dist/errors.js.map +1 -0
- package/dist/fixtures/store.d.ts +34 -0
- package/dist/fixtures/store.d.ts.map +1 -0
- package/dist/fixtures/store.js +74 -0
- package/dist/fixtures/store.js.map +1 -0
- package/dist/index.d.ts +59 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +33 -0
- package/dist/index.js.map +1 -0
- package/dist/model/answers.d.ts +38 -0
- package/dist/model/answers.d.ts.map +1 -0
- package/dist/model/answers.js +48 -0
- package/dist/model/answers.js.map +1 -0
- package/dist/model/profiles.d.ts +39 -0
- package/dist/model/profiles.d.ts.map +1 -0
- package/dist/model/profiles.js +42 -0
- package/dist/model/profiles.js.map +1 -0
- package/dist/model/types.d.ts +107 -0
- package/dist/model/types.d.ts.map +1 -0
- package/dist/model/types.js +17 -0
- package/dist/model/types.js.map +1 -0
- package/dist/model/validate.d.ts +17 -0
- package/dist/model/validate.d.ts.map +1 -0
- package/dist/model/validate.js +151 -0
- package/dist/model/validate.js.map +1 -0
- package/dist/ping.d.ts +30 -0
- package/dist/ping.d.ts.map +1 -0
- package/dist/ping.js +50 -0
- package/dist/ping.js.map +1 -0
- package/dist/prepare.d.ts +39 -0
- package/dist/prepare.d.ts.map +1 -0
- package/dist/prepare.js +84 -0
- package/dist/prepare.js.map +1 -0
- package/dist/project/project.d.ts +64 -0
- package/dist/project/project.d.ts.map +1 -0
- package/dist/project/project.js +144 -0
- package/dist/project/project.js.map +1 -0
- package/dist/run/budget.d.ts +20 -0
- package/dist/run/budget.d.ts.map +1 -0
- package/dist/run/budget.js +31 -0
- package/dist/run/budget.js.map +1 -0
- package/dist/run/pool.d.ts +16 -0
- package/dist/run/pool.d.ts.map +1 -0
- package/dist/run/pool.js +35 -0
- package/dist/run/pool.js.map +1 -0
- package/dist/run/spend.d.ts +34 -0
- package/dist/run/spend.d.ts.map +1 -0
- package/dist/run/spend.js +55 -0
- package/dist/run/spend.js.map +1 -0
- package/dist/sources/read.d.ts +40 -0
- package/dist/sources/read.d.ts.map +1 -0
- package/dist/sources/read.js +294 -0
- package/dist/sources/read.js.map +1 -0
- package/dist/sources/types.d.ts +79 -0
- package/dist/sources/types.d.ts.map +1 -0
- package/dist/sources/types.js +2 -0
- package/dist/sources/types.js.map +1 -0
- package/dist/spec/expect.d.ts +39 -0
- package/dist/spec/expect.d.ts.map +1 -0
- package/dist/spec/expect.js +95 -0
- package/dist/spec/expect.js.map +1 -0
- package/dist/spec/spec.d.ts +90 -0
- package/dist/spec/spec.d.ts.map +1 -0
- package/dist/spec/spec.js +252 -0
- package/dist/spec/spec.js.map +1 -0
- package/dist/split/split.d.ts +33 -0
- package/dist/split/split.d.ts.map +1 -0
- package/dist/split/split.js +116 -0
- package/dist/split/split.js.map +1 -0
- package/dist/testdata/index.d.ts +5 -0
- package/dist/testdata/index.d.ts.map +1 -0
- package/dist/testdata/index.js +6 -0
- package/dist/testdata/index.js.map +1 -0
- package/dist/testkit/fake-model.d.ts +14 -0
- package/dist/testkit/fake-model.d.ts.map +1 -0
- package/dist/testkit/fake-model.js +57 -0
- package/dist/testkit/fake-model.js.map +1 -0
- package/dist/testkit/tmp.d.ts +6 -0
- package/dist/testkit/tmp.d.ts.map +1 -0
- package/dist/testkit/tmp.js +46 -0
- package/dist/testkit/tmp.js.map +1 -0
- package/dist/tools/ask.d.ts +32 -0
- package/dist/tools/ask.d.ts.map +1 -0
- package/dist/tools/ask.js +59 -0
- package/dist/tools/ask.js.map +1 -0
- package/dist/tools/context.d.ts +52 -0
- package/dist/tools/context.d.ts.map +1 -0
- package/dist/tools/context.js +130 -0
- package/dist/tools/context.js.map +1 -0
- package/dist/tools/doctor.d.ts +37 -0
- package/dist/tools/doctor.d.ts.map +1 -0
- package/dist/tools/doctor.js +203 -0
- package/dist/tools/doctor.js.map +1 -0
- package/dist/tools/many.d.ts +74 -0
- package/dist/tools/many.d.ts.map +1 -0
- package/dist/tools/many.js +141 -0
- package/dist/tools/many.js.map +1 -0
- package/dist/tools/schemas.d.ts +293 -0
- package/dist/tools/schemas.d.ts.map +1 -0
- package/dist/tools/schemas.js +112 -0
- package/dist/tools/schemas.js.map +1 -0
- package/dist/tools/spec-check.d.ts +48 -0
- package/dist/tools/spec-check.d.ts.map +1 -0
- package/dist/tools/spec-check.js +164 -0
- package/dist/tools/spec-check.js.map +1 -0
- package/dist/tools/usage.d.ts +11 -0
- package/dist/tools/usage.d.ts.map +1 -0
- package/dist/tools/usage.js +27 -0
- package/dist/tools/usage.js.map +1 -0
- package/dist/transport/openrouter.d.ts +38 -0
- package/dist/transport/openrouter.d.ts.map +1 -0
- package/dist/transport/openrouter.js +89 -0
- package/dist/transport/openrouter.js.map +1 -0
- package/dist/version.d.ts +4 -0
- package/dist/version.d.ts.map +1 -0
- package/dist/version.js +5 -0
- package/dist/version.js.map +1 -0
- package/dist/wiring.d.ts +30 -0
- package/dist/wiring.d.ts.map +1 -0
- package/dist/wiring.js +31 -0
- package/dist/wiring.js.map +1 -0
- package/package.json +63 -0
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import { type ScrubCounts } from "./egress/scrub.js";
|
|
2
|
+
import type { ModelProfile } from "./model/profiles.js";
|
|
3
|
+
import type { QuestionSet } from "./model/types.js";
|
|
4
|
+
import { type Projection } from "./run/budget.js";
|
|
5
|
+
import type { Item, Skipped, SourceSpec } from "./sources/types.js";
|
|
6
|
+
import { type SplitSpec } from "./split/split.js";
|
|
7
|
+
export interface PrepareInput {
|
|
8
|
+
sources: readonly SourceSpec[];
|
|
9
|
+
split: SplitSpec;
|
|
10
|
+
questions: QuestionSet;
|
|
11
|
+
profile: ModelProfile;
|
|
12
|
+
cwd: string;
|
|
13
|
+
/** Extra exclude patterns from config. */
|
|
14
|
+
exclude?: readonly string[];
|
|
15
|
+
maxFileBytes?: number;
|
|
16
|
+
/** Allow content from outside the repo (off by default). */
|
|
17
|
+
allowOutside?: boolean;
|
|
18
|
+
}
|
|
19
|
+
export interface Prepared {
|
|
20
|
+
/** Ready to send: excluded, scrubbed and size-checked. */
|
|
21
|
+
items: Item[];
|
|
22
|
+
/** Everything withheld, with the reason. Always reported, never silent. */
|
|
23
|
+
skipped: Skipped[];
|
|
24
|
+
/** `items`: how many documents or items had at least one redaction. */
|
|
25
|
+
redactions: {
|
|
26
|
+
total: number;
|
|
27
|
+
byKind: ScrubCounts;
|
|
28
|
+
items: number;
|
|
29
|
+
};
|
|
30
|
+
projection: Projection;
|
|
31
|
+
}
|
|
32
|
+
/**
|
|
33
|
+
* sources → split → exclude → scrub → size check → projection.
|
|
34
|
+
*
|
|
35
|
+
* Nothing here touches the network. The caller then checks the budget and
|
|
36
|
+
* consent before any call is made.
|
|
37
|
+
*/
|
|
38
|
+
export declare function prepare(input: PrepareInput): Promise<Prepared>;
|
|
39
|
+
//# sourceMappingURL=prepare.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"prepare.d.ts","sourceRoot":"","sources":["../src/prepare.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,KAAK,WAAW,EAAyB,MAAM,mBAAmB,CAAA;AAG3E,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,qBAAqB,CAAA;AACvD,OAAO,KAAK,EAAE,WAAW,EAAS,MAAM,kBAAkB,CAAA;AAE1D,OAAO,EAAE,KAAK,UAAU,EAAW,MAAM,iBAAiB,CAAA;AAE1D,OAAO,KAAK,EAAE,IAAI,EAAE,OAAO,EAAE,UAAU,EAAE,MAAM,oBAAoB,CAAA;AACnE,OAAO,EAAa,KAAK,SAAS,EAAS,MAAM,kBAAkB,CAAA;AAEnE,MAAM,WAAW,YAAY;IAC3B,OAAO,EAAE,SAAS,UAAU,EAAE,CAAA;IAC9B,KAAK,EAAE,SAAS,CAAA;IAChB,SAAS,EAAE,WAAW,CAAA;IACtB,OAAO,EAAE,YAAY,CAAA;IACrB,GAAG,EAAE,MAAM,CAAA;IACX,0CAA0C;IAC1C,OAAO,CAAC,EAAE,SAAS,MAAM,EAAE,CAAA;IAC3B,YAAY,CAAC,EAAE,MAAM,CAAA;IACrB,4DAA4D;IAC5D,YAAY,CAAC,EAAE,OAAO,CAAA;CACvB;AAED,MAAM,WAAW,QAAQ;IACvB,0DAA0D;IAC1D,KAAK,EAAE,IAAI,EAAE,CAAA;IACb,2EAA2E;IAC3E,OAAO,EAAE,OAAO,EAAE,CAAA;IAClB,uEAAuE;IACvE,UAAU,EAAE;QAAE,KAAK,EAAE,MAAM,CAAC;QAAC,MAAM,EAAE,WAAW,CAAC;QAAC,KAAK,EAAE,MAAM,CAAA;KAAE,CAAA;IACjE,UAAU,EAAE,UAAU,CAAA;CACvB;AAED;;;;;GAKG;AACH,wBAAsB,OAAO,CAAC,KAAK,EAAE,YAAY,GAAG,OAAO,CAAC,QAAQ,CAAC,CA4DpE"}
|
package/dist/prepare.js
ADDED
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
import { applyExcludes } from "./egress/exclude.js";
|
|
2
|
+
import { scrubState, scrubText } from "./egress/scrub.js";
|
|
3
|
+
import { assertStateFits } from "./egress/size.js";
|
|
4
|
+
import { DecisionsError } from "./errors.js";
|
|
5
|
+
import { assertQuestionSet } from "./model/validate.js";
|
|
6
|
+
import { project } from "./run/budget.js";
|
|
7
|
+
import { readSources } from "./sources/read.js";
|
|
8
|
+
import { joinItems, split } from "./split/split.js";
|
|
9
|
+
/**
|
|
10
|
+
* sources → split → exclude → scrub → size check → projection.
|
|
11
|
+
*
|
|
12
|
+
* Nothing here touches the network. The caller then checks the budget and
|
|
13
|
+
* consent before any call is made.
|
|
14
|
+
*/
|
|
15
|
+
export async function prepare(input) {
|
|
16
|
+
assertQuestionSet(input.questions);
|
|
17
|
+
assertChoicesFit(input.questions, input.profile);
|
|
18
|
+
const read = await readSources(input.sources, {
|
|
19
|
+
cwd: input.cwd,
|
|
20
|
+
...(input.maxFileBytes ? { maxFileBytes: input.maxFileBytes } : {}),
|
|
21
|
+
...(input.allowOutside ? { allowOutside: true } : {}),
|
|
22
|
+
});
|
|
23
|
+
const byKind = {};
|
|
24
|
+
/** How many documents or items had at least one redaction. */
|
|
25
|
+
let redactedSources = 0;
|
|
26
|
+
const count = (counts) => {
|
|
27
|
+
const found = Object.values(counts).reduce((a, b) => a + b, 0);
|
|
28
|
+
for (const [kind, n] of Object.entries(counts))
|
|
29
|
+
byKind[kind] = (byKind[kind] ?? 0) + n;
|
|
30
|
+
return found;
|
|
31
|
+
};
|
|
32
|
+
// Excluded files are dropped before anything else touches them, so a
|
|
33
|
+
// withheld file is never reported as scrubbed.
|
|
34
|
+
const allowed = applyExcludes(read.documents, input.exclude);
|
|
35
|
+
// Then scrub whole documents: a private key cut across two line windows
|
|
36
|
+
// would otherwise lose the markers that identify it.
|
|
37
|
+
const documents = allowed.items.map((doc) => {
|
|
38
|
+
const counts = {};
|
|
39
|
+
const scrubbed = {
|
|
40
|
+
...doc,
|
|
41
|
+
...(doc.text !== undefined ? { text: scrubText(doc.text, counts) } : {}),
|
|
42
|
+
...(doc.data !== undefined ? { data: scrubState(doc.data, counts) } : {}),
|
|
43
|
+
};
|
|
44
|
+
if (count(counts) > 0)
|
|
45
|
+
redactedSources += 1;
|
|
46
|
+
return scrubbed;
|
|
47
|
+
});
|
|
48
|
+
// Again after splitting, in case a split produced a path we hadn't seen.
|
|
49
|
+
const filtered = applyExcludes(split(documents, input.split), input.exclude);
|
|
50
|
+
const kept = input.split.kind === "join" ? joinItems(filtered.items) : filtered.items;
|
|
51
|
+
// Then each item again, so anything the split produced (a join, a row read
|
|
52
|
+
// as a value) is covered too.
|
|
53
|
+
const tokens = [];
|
|
54
|
+
const items = kept.map((item) => {
|
|
55
|
+
const counts = {};
|
|
56
|
+
const state = scrubState(item.state, counts);
|
|
57
|
+
if (count(counts) > 0)
|
|
58
|
+
redactedSources += 1;
|
|
59
|
+
tokens.push(assertStateFits(item.id, state, input.questions, input.profile));
|
|
60
|
+
return { ...item, state };
|
|
61
|
+
});
|
|
62
|
+
return {
|
|
63
|
+
items,
|
|
64
|
+
skipped: [...read.skipped, ...allowed.excluded, ...filtered.excluded],
|
|
65
|
+
redactions: {
|
|
66
|
+
total: Object.values(byKind).reduce((a, b) => a + b, 0),
|
|
67
|
+
byKind,
|
|
68
|
+
items: redactedSources,
|
|
69
|
+
},
|
|
70
|
+
projection: project(input.profile, tokens),
|
|
71
|
+
};
|
|
72
|
+
}
|
|
73
|
+
/** Refused before any call, so a fan-out never fails N times on the same limit. */
|
|
74
|
+
function assertChoicesFit(questions, profile) {
|
|
75
|
+
for (const [name, q] of Object.entries(questions)) {
|
|
76
|
+
if (q.type !== "choice")
|
|
77
|
+
continue;
|
|
78
|
+
const n = Object.keys(q.criteria).length;
|
|
79
|
+
if (n > profile.maxChoices) {
|
|
80
|
+
throw new DecisionsError("invalid-request", `Question "${name}" has ${n} options; ${profile.id} accepts at most ${profile.maxChoices}. Narrow the candidates first (e.g. screen them with a noul), then pick among the survivors.`, { question: name, options: n, maxChoices: profile.maxChoices });
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
//# sourceMappingURL=prepare.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"prepare.js","sourceRoot":"","sources":["../src/prepare.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
import type { Answers, QuestionSet } from "../model/types.js";
|
|
2
|
+
/**
|
|
3
|
+
* Projection: the engine returns only what the agent will act on (charter
|
|
4
|
+
* principle 2). Filters, sort and field selection are parsed from a small
|
|
5
|
+
* string syntax so they fit on a command line and in a spec file.
|
|
6
|
+
*/
|
|
7
|
+
export type Op = ">=" | ">" | "<=" | "<" | "=" | "!=" | "in";
|
|
8
|
+
export interface Filter {
|
|
9
|
+
question: string;
|
|
10
|
+
/** Dotted path into the answer, e.g. `noul`, `choice`, `probabilities.fix`. */
|
|
11
|
+
field: string;
|
|
12
|
+
op: Op;
|
|
13
|
+
value: number | string | string[];
|
|
14
|
+
/** The original text, for error messages and echoing back. */
|
|
15
|
+
source: string;
|
|
16
|
+
}
|
|
17
|
+
export interface SortKey {
|
|
18
|
+
question: string;
|
|
19
|
+
field: string;
|
|
20
|
+
direction: "asc" | "desc";
|
|
21
|
+
}
|
|
22
|
+
/** Parse `relevant>=0.7`, `kind=fix`, `kind in fix,feature`, `kind.probabilities.fix>=0.5`. */
|
|
23
|
+
export declare function parseFilter(text: string, questions?: QuestionSet): Filter;
|
|
24
|
+
/** Parse `relevant`, `relevant.noul:asc`, `risk.score:desc`. Defaults to descending. */
|
|
25
|
+
export declare function parseSort(text: string, questions?: QuestionSet): SortKey;
|
|
26
|
+
export declare function matches(answers: Answers, filter: Filter): boolean;
|
|
27
|
+
export interface ProjectInput<T extends {
|
|
28
|
+
answers: Answers;
|
|
29
|
+
}> {
|
|
30
|
+
rows: readonly T[];
|
|
31
|
+
keep?: readonly Filter[];
|
|
32
|
+
sort?: SortKey;
|
|
33
|
+
limit?: number;
|
|
34
|
+
fields?: readonly string[];
|
|
35
|
+
/** Names of each row's undecided answers. */
|
|
36
|
+
undecidedOf: (row: T) => readonly string[];
|
|
37
|
+
}
|
|
38
|
+
export interface Projected<T> {
|
|
39
|
+
kept: T[];
|
|
40
|
+
/** Kept before `limit` cut it down. */
|
|
41
|
+
keptTotal: number;
|
|
42
|
+
/** Rows whose relevant answers are too flat to judge: for the agent or a human. */
|
|
43
|
+
undecided: Array<{
|
|
44
|
+
row: T;
|
|
45
|
+
questions: string[];
|
|
46
|
+
}>;
|
|
47
|
+
dropped: number;
|
|
48
|
+
}
|
|
49
|
+
/**
|
|
50
|
+
* Split rows into kept / undecided / dropped.
|
|
51
|
+
*
|
|
52
|
+
* Undecided is decided first and is never subject to a threshold. A flat
|
|
53
|
+
* answer rounded into "kept" or "dropped" would be acting on noise (charter
|
|
54
|
+
* principle 4).
|
|
55
|
+
*
|
|
56
|
+
* Every question the projection *acts on* counts: those `keep` filters on and
|
|
57
|
+
* the one `sort` ranks by. (Sorting was missed before: an item ranked first by
|
|
58
|
+
* an undecided score was kept, and reported as decided.) With neither, every
|
|
59
|
+
* question counts.
|
|
60
|
+
*/
|
|
61
|
+
export declare function project<T extends {
|
|
62
|
+
answers: Answers;
|
|
63
|
+
}>(input: ProjectInput<T>): Projected<T>;
|
|
64
|
+
//# sourceMappingURL=project.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"project.d.ts","sourceRoot":"","sources":["../../src/project/project.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAU,OAAO,EAAE,WAAW,EAAE,MAAM,mBAAmB,CAAA;AAErE;;;;GAIG;AAEH,MAAM,MAAM,EAAE,GAAG,IAAI,GAAG,GAAG,GAAG,IAAI,GAAG,GAAG,GAAG,GAAG,GAAG,IAAI,GAAG,IAAI,CAAA;AAE5D,MAAM,WAAW,MAAM;IACrB,QAAQ,EAAE,MAAM,CAAA;IAChB,+EAA+E;IAC/E,KAAK,EAAE,MAAM,CAAA;IACb,EAAE,EAAE,EAAE,CAAA;IACN,KAAK,EAAE,MAAM,GAAG,MAAM,GAAG,MAAM,EAAE,CAAA;IACjC,8DAA8D;IAC9D,MAAM,EAAE,MAAM,CAAA;CACf;AAED,MAAM,WAAW,OAAO;IACtB,QAAQ,EAAE,MAAM,CAAA;IAChB,KAAK,EAAE,MAAM,CAAA;IACb,SAAS,EAAE,KAAK,GAAG,MAAM,CAAA;CAC1B;AAID,+FAA+F;AAC/F,wBAAgB,WAAW,CAAC,IAAI,EAAE,MAAM,EAAE,SAAS,CAAC,EAAE,WAAW,GAAG,MAAM,CA2BzE;AAED,wFAAwF;AACxF,wBAAgB,SAAS,CAAC,IAAI,EAAE,MAAM,EAAE,SAAS,CAAC,EAAE,WAAW,GAAG,OAAO,CAWxE;AAED,wBAAgB,OAAO,CAAC,OAAO,EAAE,OAAO,EAAE,MAAM,EAAE,MAAM,GAAG,OAAO,CAqBjE;AAED,MAAM,WAAW,YAAY,CAAC,CAAC,SAAS;IAAE,OAAO,EAAE,OAAO,CAAA;CAAE;IAC1D,IAAI,EAAE,SAAS,CAAC,EAAE,CAAA;IAClB,IAAI,CAAC,EAAE,SAAS,MAAM,EAAE,CAAA;IACxB,IAAI,CAAC,EAAE,OAAO,CAAA;IACd,KAAK,CAAC,EAAE,MAAM,CAAA;IACd,MAAM,CAAC,EAAE,SAAS,MAAM,EAAE,CAAA;IAC1B,6CAA6C;IAC7C,WAAW,EAAE,CAAC,GAAG,EAAE,CAAC,KAAK,SAAS,MAAM,EAAE,CAAA;CAC3C;AAED,MAAM,WAAW,SAAS,CAAC,CAAC;IAC1B,IAAI,EAAE,CAAC,EAAE,CAAA;IACT,uCAAuC;IACvC,SAAS,EAAE,MAAM,CAAA;IACjB,mFAAmF;IACnF,SAAS,EAAE,KAAK,CAAC;QAAE,GAAG,EAAE,CAAC,CAAC;QAAC,SAAS,EAAE,MAAM,EAAE,CAAA;KAAE,CAAC,CAAA;IACjD,OAAO,EAAE,MAAM,CAAA;CAChB;AAED;;;;;;;;;;;GAWG;AACH,wBAAgB,OAAO,CAAC,CAAC,SAAS;IAAE,OAAO,EAAE,OAAO,CAAA;CAAE,EAAE,KAAK,EAAE,YAAY,CAAC,CAAC,CAAC,GAAG,SAAS,CAAC,CAAC,CAAC,CA2B5F"}
|
|
@@ -0,0 +1,144 @@
|
|
|
1
|
+
import { DecisionsError } from "../errors.js";
|
|
2
|
+
import { confidenceOf } from "../model/answers.js";
|
|
3
|
+
const FILTER = /^\s*([A-Za-z_][\w-]*)((?:\.[\w-]+)*)\s*(>=|<=|!=|>|<|=|\s+in\s+)\s*(.+?)\s*$/;
|
|
4
|
+
/** Parse `relevant>=0.7`, `kind=fix`, `kind in fix,feature`, `kind.probabilities.fix>=0.5`. */
|
|
5
|
+
export function parseFilter(text, questions) {
|
|
6
|
+
const m = FILTER.exec(text);
|
|
7
|
+
if (!m?.[1] || !m[3] || m[4] === undefined) {
|
|
8
|
+
throw new DecisionsError("invalid-request", `Cannot parse --keep "${text}". Use <question>[.<field>]<op><value>, e.g. relevant>=0.7 or kind in fix,feature`);
|
|
9
|
+
}
|
|
10
|
+
const question = m[1];
|
|
11
|
+
const op = m[3].trim();
|
|
12
|
+
const field = m[2] ? m[2].slice(1) : defaultField(question, questions);
|
|
13
|
+
const raw = m[4];
|
|
14
|
+
const value = op === "in"
|
|
15
|
+
? raw
|
|
16
|
+
.split(",")
|
|
17
|
+
.map((s) => s.trim())
|
|
18
|
+
.filter(Boolean)
|
|
19
|
+
: isNumeric(raw)
|
|
20
|
+
? Number(raw)
|
|
21
|
+
: raw;
|
|
22
|
+
const filter = { question, field, op, value, source: text };
|
|
23
|
+
if (questions)
|
|
24
|
+
assertKnown(question, questions, text);
|
|
25
|
+
if ((op === ">" || op === ">=" || op === "<" || op === "<=") && typeof value !== "number") {
|
|
26
|
+
throw new DecisionsError("invalid-request", `--keep "${text}": ${op} needs a number`);
|
|
27
|
+
}
|
|
28
|
+
return filter;
|
|
29
|
+
}
|
|
30
|
+
/** Parse `relevant`, `relevant.noul:asc`, `risk.score:desc`. Defaults to descending. */
|
|
31
|
+
export function parseSort(text, questions) {
|
|
32
|
+
const m = /^\s*([A-Za-z_][\w-]*)((?:\.[\w-]+)*)(?::(asc|desc))?\s*$/.exec(text);
|
|
33
|
+
if (!m?.[1]) {
|
|
34
|
+
throw new DecisionsError("invalid-request", `Cannot parse --sort "${text}". Use <question>[.<field>][:asc|desc]`);
|
|
35
|
+
}
|
|
36
|
+
if (questions)
|
|
37
|
+
assertKnown(m[1], questions, text);
|
|
38
|
+
const field = m[2] ? m[2].slice(1) : sortField(m[1], questions);
|
|
39
|
+
return { question: m[1], field, direction: m[3] ?? "desc" };
|
|
40
|
+
}
|
|
41
|
+
export function matches(answers, filter) {
|
|
42
|
+
const actual = read(answers[filter.question], filter.field);
|
|
43
|
+
switch (filter.op) {
|
|
44
|
+
case "in":
|
|
45
|
+
return filter.value.includes(String(actual));
|
|
46
|
+
case "=":
|
|
47
|
+
return String(actual) === String(filter.value);
|
|
48
|
+
case "!=":
|
|
49
|
+
return String(actual) !== String(filter.value);
|
|
50
|
+
default: {
|
|
51
|
+
if (typeof actual !== "number")
|
|
52
|
+
return false;
|
|
53
|
+
const v = filter.value;
|
|
54
|
+
return filter.op === ">="
|
|
55
|
+
? actual >= v
|
|
56
|
+
: filter.op === ">"
|
|
57
|
+
? actual > v
|
|
58
|
+
: filter.op === "<="
|
|
59
|
+
? actual <= v
|
|
60
|
+
: actual < v;
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
/**
|
|
65
|
+
* Split rows into kept / undecided / dropped.
|
|
66
|
+
*
|
|
67
|
+
* Undecided is decided first and is never subject to a threshold. A flat
|
|
68
|
+
* answer rounded into "kept" or "dropped" would be acting on noise (charter
|
|
69
|
+
* principle 4).
|
|
70
|
+
*
|
|
71
|
+
* Every question the projection *acts on* counts: those `keep` filters on and
|
|
72
|
+
* the one `sort` ranks by. (Sorting was missed before: an item ranked first by
|
|
73
|
+
* an undecided score was kept, and reported as decided.) With neither, every
|
|
74
|
+
* question counts.
|
|
75
|
+
*/
|
|
76
|
+
export function project(input) {
|
|
77
|
+
const keep = input.keep ?? [];
|
|
78
|
+
const acted = [...keep.map((f) => f.question), ...(input.sort ? [input.sort.question] : [])];
|
|
79
|
+
const relevant = acted.length > 0 ? new Set(acted) : undefined;
|
|
80
|
+
const kept = [];
|
|
81
|
+
const undecided = [];
|
|
82
|
+
let dropped = 0;
|
|
83
|
+
for (const row of input.rows) {
|
|
84
|
+
const flat = input.undecidedOf(row).filter((q) => !relevant || relevant.has(q));
|
|
85
|
+
if (flat.length > 0)
|
|
86
|
+
undecided.push({ row, questions: [...flat] });
|
|
87
|
+
else if (keep.every((f) => matches(row.answers, f)))
|
|
88
|
+
kept.push(row);
|
|
89
|
+
else
|
|
90
|
+
dropped += 1;
|
|
91
|
+
}
|
|
92
|
+
if (input.sort) {
|
|
93
|
+
const { question, field, direction } = input.sort;
|
|
94
|
+
const sign = direction === "asc" ? 1 : -1;
|
|
95
|
+
kept.sort((a, b) => sign * (num(read(a.answers[question], field)) - num(read(b.answers[question], field))));
|
|
96
|
+
}
|
|
97
|
+
const keptTotal = kept.length;
|
|
98
|
+
const limited = input.limit !== undefined ? kept.slice(0, input.limit) : kept;
|
|
99
|
+
const withFields = input.fields
|
|
100
|
+
? limited.map((row) => pickAnswers(row, input.fields))
|
|
101
|
+
: limited;
|
|
102
|
+
return { kept: withFields, keptTotal, undecided, dropped };
|
|
103
|
+
}
|
|
104
|
+
function pickAnswers(row, fields) {
|
|
105
|
+
return {
|
|
106
|
+
...row,
|
|
107
|
+
answers: Object.fromEntries(Object.entries(row.answers).filter(([k]) => fields.includes(k))),
|
|
108
|
+
};
|
|
109
|
+
}
|
|
110
|
+
function read(answer, field) {
|
|
111
|
+
if (!answer)
|
|
112
|
+
return undefined;
|
|
113
|
+
if (field === "confidence")
|
|
114
|
+
return confidenceOf(answer);
|
|
115
|
+
let value = answer;
|
|
116
|
+
for (const part of field.split(".")) {
|
|
117
|
+
value =
|
|
118
|
+
typeof value === "object" && value !== null
|
|
119
|
+
? value[part]
|
|
120
|
+
: undefined;
|
|
121
|
+
}
|
|
122
|
+
return value;
|
|
123
|
+
}
|
|
124
|
+
function defaultField(question, questions) {
|
|
125
|
+
const type = questions?.[question]?.type;
|
|
126
|
+
return type ?? "noul";
|
|
127
|
+
}
|
|
128
|
+
/** Choices sort by confidence (their winner is a label, not a number). */
|
|
129
|
+
function sortField(question, questions) {
|
|
130
|
+
const type = questions?.[question]?.type;
|
|
131
|
+
return type === "choice" ? "confidence" : (type ?? "noul");
|
|
132
|
+
}
|
|
133
|
+
function assertKnown(question, questions, text) {
|
|
134
|
+
if (!(question in questions)) {
|
|
135
|
+
throw new DecisionsError("invalid-request", `"${text}" names question "${question}", which is not in the question set (${Object.keys(questions).join(", ")})`);
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
function isNumeric(text) {
|
|
139
|
+
return /^-?\d+(\.\d+)?$/.test(text);
|
|
140
|
+
}
|
|
141
|
+
function num(value) {
|
|
142
|
+
return typeof value === "number" ? value : Number.NEGATIVE_INFINITY;
|
|
143
|
+
}
|
|
144
|
+
//# sourceMappingURL=project.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"project.js","sourceRoot":"","sources":["../../src/project/project.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import type { Budget } from "../config/load.js";
|
|
2
|
+
import type { ModelProfile } from "../model/profiles.js";
|
|
3
|
+
/** A cost figure computed before running. Always labelled; never shown as measured. */
|
|
4
|
+
export interface Projection {
|
|
5
|
+
basis: "projected";
|
|
6
|
+
calls: number;
|
|
7
|
+
estimatedInputTokens: number;
|
|
8
|
+
projectedUsd: number;
|
|
9
|
+
/** Listed-price date the projection used. */
|
|
10
|
+
priceAsOf: string;
|
|
11
|
+
}
|
|
12
|
+
export declare function project(profile: ModelProfile, tokensPerCall: readonly number[]): Projection;
|
|
13
|
+
/**
|
|
14
|
+
* The spend guard (decision 0011): above either limit, stop and hand back the
|
|
15
|
+
* projection unless the caller explicitly confirmed.
|
|
16
|
+
*
|
|
17
|
+
* @throws DecisionsError `budget-exceeded`, with the projection in `details`.
|
|
18
|
+
*/
|
|
19
|
+
export declare function checkBudget(projection: Projection, budget: Budget, confirmed: boolean): void;
|
|
20
|
+
//# sourceMappingURL=budget.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"budget.d.ts","sourceRoot":"","sources":["../../src/run/budget.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,mBAAmB,CAAA;AAE/C,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,sBAAsB,CAAA;AAExD,uFAAuF;AACvF,MAAM,WAAW,UAAU;IACzB,KAAK,EAAE,WAAW,CAAA;IAClB,KAAK,EAAE,MAAM,CAAA;IACb,oBAAoB,EAAE,MAAM,CAAA;IAC5B,YAAY,EAAE,MAAM,CAAA;IACpB,6CAA6C;IAC7C,SAAS,EAAE,MAAM,CAAA;CAClB;AAED,wBAAgB,OAAO,CAAC,OAAO,EAAE,YAAY,EAAE,aAAa,EAAE,SAAS,MAAM,EAAE,GAAG,UAAU,CAS3F;AAED;;;;;GAKG;AACH,wBAAgB,WAAW,CAAC,UAAU,EAAE,UAAU,EAAE,MAAM,EAAE,MAAM,EAAE,SAAS,EAAE,OAAO,GAAG,IAAI,CAc5F"}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import { DecisionsError } from "../errors.js";
|
|
2
|
+
export function project(profile, tokensPerCall) {
|
|
3
|
+
const estimatedInputTokens = tokensPerCall.reduce((a, b) => a + b, 0);
|
|
4
|
+
return {
|
|
5
|
+
basis: "projected",
|
|
6
|
+
calls: tokensPerCall.length,
|
|
7
|
+
estimatedInputTokens,
|
|
8
|
+
projectedUsd: estimatedInputTokens * profile.usdPerInputToken,
|
|
9
|
+
priceAsOf: profile.priceAsOf,
|
|
10
|
+
};
|
|
11
|
+
}
|
|
12
|
+
/**
|
|
13
|
+
* The spend guard (decision 0011): above either limit, stop and hand back the
|
|
14
|
+
* projection unless the caller explicitly confirmed.
|
|
15
|
+
*
|
|
16
|
+
* @throws DecisionsError `budget-exceeded`, with the projection in `details`.
|
|
17
|
+
*/
|
|
18
|
+
export function checkBudget(projection, budget, confirmed) {
|
|
19
|
+
if (confirmed)
|
|
20
|
+
return;
|
|
21
|
+
const over = [];
|
|
22
|
+
if (projection.calls > budget.maxCalls)
|
|
23
|
+
over.push(`${projection.calls} calls > ${budget.maxCalls}`);
|
|
24
|
+
if (projection.projectedUsd > budget.maxUsd) {
|
|
25
|
+
over.push(`projected $${projection.projectedUsd.toFixed(4)} > $${budget.maxUsd}`);
|
|
26
|
+
}
|
|
27
|
+
if (over.length === 0)
|
|
28
|
+
return;
|
|
29
|
+
throw new DecisionsError("budget-exceeded", `Over the spend guard (${over.join("; ")}). Narrow the source, or re-run with --confirm to proceed.`, { projection, budget });
|
|
30
|
+
}
|
|
31
|
+
//# sourceMappingURL=budget.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"budget.js","sourceRoot":"","sources":["../../src/run/budget.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,cAAc,EAAE,MAAM,cAAc,CAAA;AAa7C,MAAM,UAAU,OAAO,CAAC,OAAqB,EAAE,aAAgC;IAC7E,MAAM,oBAAoB,GAAG,aAAa,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC,CAAA;IACrE,OAAO;QACL,KAAK,EAAE,WAAW;QAClB,KAAK,EAAE,aAAa,CAAC,MAAM;QAC3B,oBAAoB;QACpB,YAAY,EAAE,oBAAoB,GAAG,OAAO,CAAC,gBAAgB;QAC7D,SAAS,EAAE,OAAO,CAAC,SAAS;KAC7B,CAAA;AACH,CAAC;AAED;;;;;GAKG;AACH,MAAM,UAAU,WAAW,CAAC,UAAsB,EAAE,MAAc,EAAE,SAAkB;IACpF,IAAI,SAAS;QAAE,OAAM;IACrB,MAAM,IAAI,GAAa,EAAE,CAAA;IACzB,IAAI,UAAU,CAAC,KAAK,GAAG,MAAM,CAAC,QAAQ;QACpC,IAAI,CAAC,IAAI,CAAC,GAAG,UAAU,CAAC,KAAK,YAAY,MAAM,CAAC,QAAQ,EAAE,CAAC,CAAA;IAC7D,IAAI,UAAU,CAAC,YAAY,GAAG,MAAM,CAAC,MAAM,EAAE,CAAC;QAC5C,IAAI,CAAC,IAAI,CAAC,cAAc,UAAU,CAAC,YAAY,CAAC,OAAO,CAAC,CAAC,CAAC,OAAO,MAAM,CAAC,MAAM,EAAE,CAAC,CAAA;IACnF,CAAC;IACD,IAAI,IAAI,CAAC,MAAM,KAAK,CAAC;QAAE,OAAM;IAC7B,MAAM,IAAI,cAAc,CACtB,iBAAiB,EACjB,yBAAyB,IAAI,CAAC,IAAI,CAAC,IAAI,CAAC,4DAA4D,EACpG,EAAE,UAAU,EAAE,MAAM,EAAE,CACvB,CAAA;AACH,CAAC"}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Run `worker` over every item, never more than `limit` at once.
|
|
3
|
+
*
|
|
4
|
+
* Results come back in input order whatever the completion order, so callers
|
|
5
|
+
* can zip them against their inputs by index.
|
|
6
|
+
*
|
|
7
|
+
* A rejected worker never aborts the run: in a wide fan-out, one transient
|
|
8
|
+
* upstream error would otherwise throw away every sibling result that was
|
|
9
|
+
* already paid for. Failures are captured per item instead.
|
|
10
|
+
*/
|
|
11
|
+
export declare function mapWithConcurrency<T, R>(items: readonly T[], limit: number, worker: (item: T, index: number) => Promise<R>, onProgress?: (completed: number, total: number) => void): Promise<Array<{
|
|
12
|
+
value: R;
|
|
13
|
+
} | {
|
|
14
|
+
error: Error;
|
|
15
|
+
}>>;
|
|
16
|
+
//# sourceMappingURL=pool.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"pool.d.ts","sourceRoot":"","sources":["../../src/run/pool.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AACH,wBAAsB,kBAAkB,CAAC,CAAC,EAAE,CAAC,EAC3C,KAAK,EAAE,SAAS,CAAC,EAAE,EACnB,KAAK,EAAE,MAAM,EACb,MAAM,EAAE,CAAC,IAAI,EAAE,CAAC,EAAE,KAAK,EAAE,MAAM,KAAK,OAAO,CAAC,CAAC,CAAC,EAC9C,UAAU,CAAC,EAAE,CAAC,SAAS,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,KAAK,IAAI,GACtD,OAAO,CAAC,KAAK,CAAC;IAAE,KAAK,EAAE,CAAC,CAAA;CAAE,GAAG;IAAE,KAAK,EAAE,KAAK,CAAA;CAAE,CAAC,CAAC,CAuBjD"}
|
package/dist/run/pool.js
ADDED
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Run `worker` over every item, never more than `limit` at once.
|
|
3
|
+
*
|
|
4
|
+
* Results come back in input order whatever the completion order, so callers
|
|
5
|
+
* can zip them against their inputs by index.
|
|
6
|
+
*
|
|
7
|
+
* A rejected worker never aborts the run: in a wide fan-out, one transient
|
|
8
|
+
* upstream error would otherwise throw away every sibling result that was
|
|
9
|
+
* already paid for. Failures are captured per item instead.
|
|
10
|
+
*/
|
|
11
|
+
export async function mapWithConcurrency(items, limit, worker, onProgress) {
|
|
12
|
+
const width = Math.max(1, Math.min(limit, items.length || 1));
|
|
13
|
+
const results = new Array(items.length);
|
|
14
|
+
let next = 0;
|
|
15
|
+
let completed = 0;
|
|
16
|
+
async function run() {
|
|
17
|
+
while (true) {
|
|
18
|
+
const index = next;
|
|
19
|
+
next += 1;
|
|
20
|
+
if (index >= items.length)
|
|
21
|
+
return;
|
|
22
|
+
try {
|
|
23
|
+
results[index] = { value: await worker(items[index], index) };
|
|
24
|
+
}
|
|
25
|
+
catch (error) {
|
|
26
|
+
results[index] = { error: error instanceof Error ? error : new Error(String(error)) };
|
|
27
|
+
}
|
|
28
|
+
completed += 1;
|
|
29
|
+
onProgress?.(completed, items.length);
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
await Promise.all(Array.from({ length: width }, run));
|
|
33
|
+
return results;
|
|
34
|
+
}
|
|
35
|
+
//# sourceMappingURL=pool.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"pool.js","sourceRoot":"","sources":["../../src/run/pool.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AACH,MAAM,CAAC,KAAK,UAAU,kBAAkB,CACtC,KAAmB,EACnB,KAAa,EACb,MAA8C,EAC9C,UAAuD;IAEvD,MAAM,KAAK,GAAG,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,IAAI,CAAC,GAAG,CAAC,KAAK,EAAE,KAAK,CAAC,MAAM,IAAI,CAAC,CAAC,CAAC,CAAA;IAC7D,MAAM,OAAO,GAAG,IAAI,KAAK,CAAkC,KAAK,CAAC,MAAM,CAAC,CAAA;IACxE,IAAI,IAAI,GAAG,CAAC,CAAA;IACZ,IAAI,SAAS,GAAG,CAAC,CAAA;IAEjB,KAAK,UAAU,GAAG;QAChB,OAAO,IAAI,EAAE,CAAC;YACZ,MAAM,KAAK,GAAG,IAAI,CAAA;YAClB,IAAI,IAAI,CAAC,CAAA;YACT,IAAI,KAAK,IAAI,KAAK,CAAC,MAAM;gBAAE,OAAM;YACjC,IAAI,CAAC;gBACH,OAAO,CAAC,KAAK,CAAC,GAAG,EAAE,KAAK,EAAE,MAAM,MAAM,CAAC,KAAK,CAAC,KAAK,CAAM,EAAE,KAAK,CAAC,EAAE,CAAA;YACpE,CAAC;YAAC,OAAO,KAAK,EAAE,CAAC;gBACf,OAAO,CAAC,KAAK,CAAC,GAAG,EAAE,KAAK,EAAE,KAAK,YAAY,KAAK,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,IAAI,KAAK,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC,EAAE,CAAA;YACvF,CAAC;YACD,SAAS,IAAI,CAAC,CAAA;YACd,UAAU,EAAE,CAAC,SAAS,EAAE,KAAK,CAAC,MAAM,CAAC,CAAA;QACvC,CAAC;IACH,CAAC;IAED,MAAM,OAAO,CAAC,GAAG,CAAC,KAAK,CAAC,IAAI,CAAC,EAAE,MAAM,EAAE,KAAK,EAAE,EAAE,GAAG,CAAC,CAAC,CAAA;IACrD,OAAO,OAAO,CAAA;AAChB,CAAC"}
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import type { Usage } from "../model/types.js";
|
|
2
|
+
export type AnswerSource = "live" | "replay";
|
|
3
|
+
/** One ledger line. Every figure comes from an upstream `usage` block; nothing is estimated. */
|
|
4
|
+
export interface SpendEntry extends Usage {
|
|
5
|
+
ts: string;
|
|
6
|
+
session?: string;
|
|
7
|
+
model: string;
|
|
8
|
+
source: AnswerSource;
|
|
9
|
+
calls: number;
|
|
10
|
+
}
|
|
11
|
+
export interface SpendSummary extends Usage {
|
|
12
|
+
calls: number;
|
|
13
|
+
liveCalls: number;
|
|
14
|
+
replayCalls: number;
|
|
15
|
+
}
|
|
16
|
+
export declare function sumUsage(usages: ReadonlyArray<Usage | undefined>): Usage;
|
|
17
|
+
/**
|
|
18
|
+
* Measured spend, persisted as append-only JSONL (default `.system1/usage.jsonl`).
|
|
19
|
+
*
|
|
20
|
+
* A file rather than process memory because the CLI is a fresh process per call:
|
|
21
|
+
* "what has this session cost" has to survive between invocations. Replayed
|
|
22
|
+
* calls are logged at zero cost so the call count stays honest.
|
|
23
|
+
*/
|
|
24
|
+
export declare class SpendLedger {
|
|
25
|
+
readonly file: string;
|
|
26
|
+
constructor(file: string);
|
|
27
|
+
append(entry: SpendEntry): void;
|
|
28
|
+
entries(): SpendEntry[];
|
|
29
|
+
summary(filter?: {
|
|
30
|
+
session?: string;
|
|
31
|
+
since?: Date;
|
|
32
|
+
}): SpendSummary;
|
|
33
|
+
}
|
|
34
|
+
//# sourceMappingURL=spend.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"spend.d.ts","sourceRoot":"","sources":["../../src/run/spend.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,KAAK,EAAE,MAAM,mBAAmB,CAAA;AAE9C,MAAM,MAAM,YAAY,GAAG,MAAM,GAAG,QAAQ,CAAA;AAE5C,gGAAgG;AAChG,MAAM,WAAW,UAAW,SAAQ,KAAK;IACvC,EAAE,EAAE,MAAM,CAAA;IACV,OAAO,CAAC,EAAE,MAAM,CAAA;IAChB,KAAK,EAAE,MAAM,CAAA;IACb,MAAM,EAAE,YAAY,CAAA;IACpB,KAAK,EAAE,MAAM,CAAA;CACd;AAED,MAAM,WAAW,YAAa,SAAQ,KAAK;IACzC,KAAK,EAAE,MAAM,CAAA;IACb,SAAS,EAAE,MAAM,CAAA;IACjB,WAAW,EAAE,MAAM,CAAA;CACpB;AAED,wBAAgB,QAAQ,CAAC,MAAM,EAAE,aAAa,CAAC,KAAK,GAAG,SAAS,CAAC,GAAG,KAAK,CAWxE;AAED;;;;;;GAMG;AACH,qBAAa,WAAW;IACV,QAAQ,CAAC,IAAI,EAAE,MAAM;IAAjC,YAAqB,IAAI,EAAE,MAAM,EAAI;IAErC,MAAM,CAAC,KAAK,EAAE,UAAU,GAAG,IAAI,CAG9B;IAED,OAAO,IAAI,UAAU,EAAE,CAYtB;IAED,OAAO,CAAC,MAAM,GAAE;QAAE,OAAO,CAAC,EAAE,MAAM,CAAC;QAAC,KAAK,CAAC,EAAE,IAAI,CAAA;KAAO,GAAG,YAAY,CAcrE;CACF"}
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
import { appendFileSync, existsSync, mkdirSync, readFileSync } from "node:fs";
|
|
2
|
+
import { dirname } from "node:path";
|
|
3
|
+
export function sumUsage(usages) {
|
|
4
|
+
const total = usages.reduce((sum, u) => ({
|
|
5
|
+
input_tokens: sum.input_tokens + (u?.input_tokens ?? 0),
|
|
6
|
+
output_tokens: sum.output_tokens + (u?.output_tokens ?? 0),
|
|
7
|
+
cost: sum.cost + (u?.cost ?? 0),
|
|
8
|
+
}), { input_tokens: 0, output_tokens: 0, cost: 0 });
|
|
9
|
+
// One unreported call makes the total a lower bound, so say so.
|
|
10
|
+
return usages.some((u) => u?.reported === false) ? { ...total, reported: false } : total;
|
|
11
|
+
}
|
|
12
|
+
/**
|
|
13
|
+
* Measured spend, persisted as append-only JSONL (default `.system1/usage.jsonl`).
|
|
14
|
+
*
|
|
15
|
+
* A file rather than process memory because the CLI is a fresh process per call:
|
|
16
|
+
* "what has this session cost" has to survive between invocations. Replayed
|
|
17
|
+
* calls are logged at zero cost so the call count stays honest.
|
|
18
|
+
*/
|
|
19
|
+
export class SpendLedger {
|
|
20
|
+
file;
|
|
21
|
+
constructor(file) {
|
|
22
|
+
this.file = file;
|
|
23
|
+
}
|
|
24
|
+
append(entry) {
|
|
25
|
+
mkdirSync(dirname(this.file), { recursive: true });
|
|
26
|
+
appendFileSync(this.file, `${JSON.stringify(entry)}\n`);
|
|
27
|
+
}
|
|
28
|
+
entries() {
|
|
29
|
+
if (!existsSync(this.file))
|
|
30
|
+
return [];
|
|
31
|
+
return readFileSync(this.file, "utf8")
|
|
32
|
+
.split("\n")
|
|
33
|
+
.filter((line) => line.trim() !== "")
|
|
34
|
+
.flatMap((line) => {
|
|
35
|
+
try {
|
|
36
|
+
return [JSON.parse(line)];
|
|
37
|
+
}
|
|
38
|
+
catch {
|
|
39
|
+
return []; // a torn last line from a crash is skipped, not fatal
|
|
40
|
+
}
|
|
41
|
+
});
|
|
42
|
+
}
|
|
43
|
+
summary(filter = {}) {
|
|
44
|
+
const rows = this.entries().filter((e) => (filter.session === undefined || e.session === filter.session) &&
|
|
45
|
+
(filter.since === undefined || new Date(e.ts) >= filter.since));
|
|
46
|
+
const count = (source) => rows.filter((e) => e.source === source).reduce((n, e) => n + e.calls, 0);
|
|
47
|
+
return {
|
|
48
|
+
...sumUsage(rows),
|
|
49
|
+
calls: rows.reduce((n, e) => n + e.calls, 0),
|
|
50
|
+
liveCalls: count("live"),
|
|
51
|
+
replayCalls: count("replay"),
|
|
52
|
+
};
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
//# sourceMappingURL=spend.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"spend.js","sourceRoot":"","sources":["../../src/run/spend.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import type { Document, LineRange, Skipped, SourceSpec } from "./types.js";
|
|
2
|
+
export interface ReadOptions {
|
|
3
|
+
/** Paths resolve against this directory (normally the repo root). */
|
|
4
|
+
cwd: string;
|
|
5
|
+
/** Files larger than this are skipped as `too-large` without being read. */
|
|
6
|
+
maxFileBytes?: number;
|
|
7
|
+
/**
|
|
8
|
+
* Allow content from outside `cwd`. Off by default: consent is given per
|
|
9
|
+
* repo, so a path (or a symlink) that resolves elsewhere is withheld.
|
|
10
|
+
*/
|
|
11
|
+
allowOutside?: boolean;
|
|
12
|
+
}
|
|
13
|
+
export interface ReadResult {
|
|
14
|
+
documents: Document[];
|
|
15
|
+
skipped: Skipped[];
|
|
16
|
+
}
|
|
17
|
+
export declare function readSources(specs: readonly SourceSpec[], options: ReadOptions): Promise<ReadResult>;
|
|
18
|
+
/**
|
|
19
|
+
* Parse `path`, `path:12` or `path:12-40` (1-based, inclusive). A bare `:`
|
|
20
|
+
* suffix that isn't a line spec stays part of the path.
|
|
21
|
+
*/
|
|
22
|
+
export declare function parseFileRef(ref: string): {
|
|
23
|
+
path: string;
|
|
24
|
+
range?: LineRange;
|
|
25
|
+
};
|
|
26
|
+
/**
|
|
27
|
+
* The subset of `paths` git would ignore, when `cwd` is inside a work tree.
|
|
28
|
+
* Outside one, nothing is filtered (and `node_modules`/`.git` are still skipped).
|
|
29
|
+
*/
|
|
30
|
+
export declare function gitIgnored(cwd: string, paths: readonly string[]): string[];
|
|
31
|
+
export declare function splitDiffByFile(diff: string): {
|
|
32
|
+
documents: Document[];
|
|
33
|
+
skipped: Skipped[];
|
|
34
|
+
};
|
|
35
|
+
/**
|
|
36
|
+
* Git quotes paths with unusual bytes (`"secrets/\303\251.txt"`). Left quoted,
|
|
37
|
+
* the path never matches an exclude, so it is decoded here.
|
|
38
|
+
*/
|
|
39
|
+
export declare function unquoteGitPath(path: string): string | undefined;
|
|
40
|
+
//# sourceMappingURL=read.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"read.d.ts","sourceRoot":"","sources":["../../src/sources/read.ts"],"names":[],"mappings":"AAKA,OAAO,KAAK,EAAE,QAAQ,EAAE,SAAS,EAAE,OAAO,EAAE,UAAU,EAAE,MAAM,YAAY,CAAA;AAE1E,MAAM,WAAW,WAAW;IAC1B,qEAAqE;IACrE,GAAG,EAAE,MAAM,CAAA;IACX,4EAA4E;IAC5E,YAAY,CAAC,EAAE,MAAM,CAAA;IACrB;;;OAGG;IACH,YAAY,CAAC,EAAE,OAAO,CAAA;CACvB;AAgCD,MAAM,WAAW,UAAU;IACzB,SAAS,EAAE,QAAQ,EAAE,CAAA;IACrB,OAAO,EAAE,OAAO,EAAE,CAAA;CACnB;AAQD,wBAAsB,WAAW,CAC/B,KAAK,EAAE,SAAS,UAAU,EAAE,EAC5B,OAAO,EAAE,WAAW,GACnB,OAAO,CAAC,UAAU,CAAC,CASrB;AAmBD;;;GAGG;AACH,wBAAgB,YAAY,CAAC,GAAG,EAAE,MAAM,GAAG;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,KAAK,CAAC,EAAE,SAAS,CAAA;CAAE,CAY7E;AAwED;;;GAGG;AACH,wBAAgB,UAAU,CAAC,GAAG,EAAE,MAAM,EAAE,KAAK,EAAE,SAAS,MAAM,EAAE,GAAG,MAAM,EAAE,CAwB1E;AAyED,wBAAgB,eAAe,CAAC,IAAI,EAAE,MAAM,GAAG;IAAE,SAAS,EAAE,QAAQ,EAAE,CAAC;IAAC,OAAO,EAAE,OAAO,EAAE,CAAA;CAAE,CAsB3F;AAED;;;GAGG;AACH,wBAAgB,cAAc,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,GAAG,SAAS,CAyB/D"}
|