@leanandmean/coding-agent 0.74.1-scramjet.35 → 0.74.1-scramjet.40

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.
Files changed (48) hide show
  1. package/CHANGELOG.md +6 -0
  2. package/README.md +1 -1
  3. package/dist/core/agent-session-runtime.d.ts.map +1 -1
  4. package/dist/core/agent-session-runtime.js +1 -0
  5. package/dist/core/agent-session-runtime.js.map +1 -1
  6. package/dist/core/agent-session-services.d.ts +1 -0
  7. package/dist/core/agent-session-services.d.ts.map +1 -1
  8. package/dist/core/agent-session-services.js +2 -0
  9. package/dist/core/agent-session-services.js.map +1 -1
  10. package/dist/core/agent-session.d.ts +15 -0
  11. package/dist/core/agent-session.d.ts.map +1 -1
  12. package/dist/core/agent-session.js +90 -0
  13. package/dist/core/agent-session.js.map +1 -1
  14. package/dist/core/output-throughput-history.d.ts +25 -0
  15. package/dist/core/output-throughput-history.d.ts.map +1 -0
  16. package/dist/core/output-throughput-history.js +240 -0
  17. package/dist/core/output-throughput-history.js.map +1 -0
  18. package/dist/core/output-throughput.d.ts +43 -0
  19. package/dist/core/output-throughput.d.ts.map +1 -0
  20. package/dist/core/output-throughput.js +161 -0
  21. package/dist/core/output-throughput.js.map +1 -0
  22. package/dist/core/sdk.d.ts +2 -0
  23. package/dist/core/sdk.d.ts.map +1 -1
  24. package/dist/core/sdk.js +3 -0
  25. package/dist/core/sdk.js.map +1 -1
  26. package/dist/core/skills.d.ts.map +1 -1
  27. package/dist/core/skills.js +6 -2
  28. package/dist/core/skills.js.map +1 -1
  29. package/dist/modes/interactive/components/footer.d.ts.map +1 -1
  30. package/dist/modes/interactive/components/footer.js +6 -2
  31. package/dist/modes/interactive/components/footer.js.map +1 -1
  32. package/dist/modes/interactive/components/model-selector.d.ts +3 -1
  33. package/dist/modes/interactive/components/model-selector.d.ts.map +1 -1
  34. package/dist/modes/interactive/components/model-selector.js +9 -3
  35. package/dist/modes/interactive/components/model-selector.js.map +1 -1
  36. package/dist/modes/interactive/components/scoped-models-selector.d.ts +3 -1
  37. package/dist/modes/interactive/components/scoped-models-selector.d.ts.map +1 -1
  38. package/dist/modes/interactive/components/scoped-models-selector.js +8 -2
  39. package/dist/modes/interactive/components/scoped-models-selector.js.map +1 -1
  40. package/dist/modes/interactive/interactive-mode.d.ts +3 -0
  41. package/dist/modes/interactive/interactive-mode.d.ts.map +1 -1
  42. package/dist/modes/interactive/interactive-mode.js +45 -7
  43. package/dist/modes/interactive/interactive-mode.js.map +1 -1
  44. package/docs/models.md +2 -0
  45. package/docs/prompt-templates.md +2 -0
  46. package/docs/skills.md +7 -5
  47. package/docs/usage.md +3 -1
  48. package/package.json +8 -4
@@ -0,0 +1,25 @@
1
+ import { OutputThroughputHistory, type OutputThroughputSample } from "./output-throughput.js";
2
+ export interface OutputThroughputHistoryDiagnostic {
3
+ operation: "refresh" | "lock" | "read" | "validate" | "write" | "rename" | "unlink" | "unlock";
4
+ path: string;
5
+ message: string;
6
+ cause?: unknown;
7
+ }
8
+ export declare class OutputThroughputHistoryStore {
9
+ readonly path: string;
10
+ private readonly onDiagnostic?;
11
+ private history;
12
+ private queueKey?;
13
+ private pending;
14
+ private readonly reportedDiagnostics;
15
+ constructor(path: string, onDiagnostic?: ((diagnostic: OutputThroughputHistoryDiagnostic) => void) | undefined);
16
+ snapshot(): OutputThroughputHistory;
17
+ refresh(): Promise<OutputThroughputHistory>;
18
+ submit(sample: OutputThroughputSample): void;
19
+ flush(): Promise<void>;
20
+ private update;
21
+ private readDocument;
22
+ private enqueue;
23
+ private report;
24
+ }
25
+ //# sourceMappingURL=output-throughput-history.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"output-throughput-history.d.ts","sourceRoot":"","sources":["../../src/core/output-throughput-history.ts"],"names":[],"mappings":"AAIA,OAAO,EAAE,uBAAuB,EAAE,KAAK,sBAAsB,EAAE,MAAM,wBAAwB,CAAC;AAU9F,MAAM,WAAW,iCAAiC;IACjD,SAAS,EAAE,SAAS,GAAG,MAAM,GAAG,MAAM,GAAG,UAAU,GAAG,OAAO,GAAG,QAAQ,GAAG,QAAQ,GAAG,QAAQ,CAAC;IAC/F,IAAI,EAAE,MAAM,CAAC;IACb,OAAO,EAAE,MAAM,CAAC;IAChB,KAAK,CAAC,EAAE,OAAO,CAAC;CAChB;AAOD,qBAAa,4BAA4B;IAOvC,QAAQ,CAAC,IAAI,EAAE,MAAM;IACrB,OAAO,CAAC,QAAQ,CAAC,YAAY,CAAC;IAP/B,OAAO,CAAC,OAAO,CAAiC;IAChD,OAAO,CAAC,QAAQ,CAAC,CAAkB;IACnC,OAAO,CAAC,OAAO,CAAoC;IACnD,OAAO,CAAC,QAAQ,CAAC,mBAAmB,CAAqB;IAEzD,YACU,IAAI,EAAE,MAAM,EACJ,YAAY,CAAC,uEAAyD,EACpF;IAEJ,QAAQ,IAAI,uBAAuB,CAElC;IAEK,OAAO,IAAI,OAAO,CAAC,uBAAuB,CAAC,CAMhD;IAED,MAAM,CAAC,MAAM,EAAE,sBAAsB,GAAG,IAAI,CAO3C;IAEK,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC,CAE3B;YAEa,MAAM;YAqDN,YAAY;YA2BZ,OAAO;IAwBrB,OAAO,CAAC,MAAM;CAOd","sourcesContent":["import { randomUUID } from \"node:crypto\";\nimport { mkdir, open, readFile, realpath, rename, unlink, writeFile } from \"node:fs/promises\";\nimport { basename, dirname, join, resolve } from \"node:path\";\nimport lockfile from \"proper-lockfile\";\nimport { OutputThroughputHistory, type OutputThroughputSample } from \"./output-throughput.js\";\n\n// SCRAMJET-DIVERGENCE: Persist bounded, profile-scoped provider throughput across concurrent processes (#476).\n\nconst HISTORY_VERSION = 1;\nconst MAX_SAMPLES_PER_MODEL = 20;\nconst MAX_HISTORY_SAMPLES = 20000;\nconst MAX_STRING_LENGTH = 512;\nconst queues = new Map<string, Promise<void>>();\n\nexport interface OutputThroughputHistoryDiagnostic {\n\toperation: \"refresh\" | \"lock\" | \"read\" | \"validate\" | \"write\" | \"rename\" | \"unlink\" | \"unlock\";\n\tpath: string;\n\tmessage: string;\n\tcause?: unknown;\n}\n\ninterface HistoryDocument {\n\tversion: 1;\n\tsamples: OutputThroughputSample[];\n}\n\nexport class OutputThroughputHistoryStore {\n\tprivate history = new OutputThroughputHistory();\n\tprivate queueKey?: Promise<string>;\n\tprivate pending: Promise<void> = Promise.resolve();\n\tprivate readonly reportedDiagnostics = new Set<string>();\n\n\tconstructor(\n\t\treadonly path: string,\n\t\tprivate readonly onDiagnostic?: (diagnostic: OutputThroughputHistoryDiagnostic) => void,\n\t) {}\n\n\tsnapshot(): OutputThroughputHistory {\n\t\treturn historyFromSamples(this.history.allSamples());\n\t}\n\n\tasync refresh(): Promise<OutputThroughputHistory> {\n\t\tawait this.enqueue(async () => {\n\t\t\tconst document = await this.readDocument(\"refresh\");\n\t\t\tif (document) this.history = historyFromSamples(document.samples);\n\t\t});\n\t\treturn this.snapshot();\n\t}\n\n\tsubmit(sample: OutputThroughputSample): void {\n\t\tif (!isValidSample(sample)) {\n\t\t\tthis.report(\"validate\", \"Invalid output-throughput sample was not persisted; existing history was preserved\");\n\t\t\treturn;\n\t\t}\n\t\tconst normalized = normalizeSample(sample);\n\t\tthis.pending = this.enqueue(() => this.update(normalized));\n\t}\n\n\tasync flush(): Promise<void> {\n\t\tawait this.pending;\n\t}\n\n\tprivate async update(sample: OutputThroughputSample): Promise<void> {\n\t\tawait mkdir(dirname(this.path), { recursive: true });\n\t\tconst anchorPath = join(dirname(this.path), `${basename(this.path, \".json\")}.lock`);\n\t\ttry {\n\t\t\tawait writeFile(anchorPath, \"\", { flag: \"a\" });\n\t\t} catch (cause) {\n\t\t\tthis.report(\"lock\", \"Could not prepare the throughput-history lock; existing history was preserved\", cause);\n\t\t\treturn;\n\t\t}\n\n\t\tlet release: (() => Promise<void>) | undefined;\n\t\ttry {\n\t\t\trelease = await lockfile.lock(anchorPath, {\n\t\t\t\trealpath: false,\n\t\t\t\tretries: { retries: 8, factor: 2, minTimeout: 25, maxTimeout: 1000, randomize: true },\n\t\t\t\tstale: 30000,\n\t\t\t});\n\t\t\tconst current = await this.readDocument(\"read\");\n\t\t\tif (!current) return;\n\t\t\tconst samples = mergeSamples(current.samples, sample);\n\t\t\tconst document: HistoryDocument = { version: HISTORY_VERSION, samples };\n\t\t\tconst temporaryPath = join(dirname(this.path), `.${basename(this.path)}.${process.pid}.${randomUUID()}.tmp`);\n\t\t\tlet temporaryCreated = false;\n\t\t\ttry {\n\t\t\t\tconst handle = await open(temporaryPath, \"wx\", 0o600);\n\t\t\t\ttemporaryCreated = true;\n\t\t\t\ttry {\n\t\t\t\t\tawait handle.writeFile(`${JSON.stringify(document, null, 2)}\\n`, \"utf8\");\n\t\t\t\t\tawait handle.sync();\n\t\t\t\t} finally {\n\t\t\t\t\tawait handle.close();\n\t\t\t\t}\n\t\t\t\tawait rename(temporaryPath, this.path);\n\t\t\t\ttemporaryCreated = false;\n\t\t\t\tthis.history = historyFromSamples(samples);\n\t\t\t} catch (cause) {\n\t\t\t\tthis.report(\"write\", \"Could not commit output-throughput history; existing history was preserved\", cause);\n\t\t\t} finally {\n\t\t\t\tif (temporaryCreated) {\n\t\t\t\t\tawait unlink(temporaryPath).catch((cause) => {\n\t\t\t\t\t\tthis.report(\"unlink\", \"Could not remove the temporary output-throughput history file\", cause);\n\t\t\t\t\t});\n\t\t\t\t}\n\t\t\t}\n\t\t} catch (cause) {\n\t\t\tthis.report(\"lock\", \"Could not lock output-throughput history; existing history was preserved\", cause);\n\t\t} finally {\n\t\t\tawait release?.().catch((cause) => {\n\t\t\t\tthis.report(\"unlock\", \"Could not release the output-throughput history lock\", cause);\n\t\t\t});\n\t\t}\n\t}\n\n\tprivate async readDocument(operation: \"refresh\" | \"read\"): Promise<HistoryDocument | undefined> {\n\t\tlet raw: string;\n\t\ttry {\n\t\t\traw = await readFile(this.path, \"utf8\");\n\t\t} catch (cause) {\n\t\t\tif (isMissingFile(cause)) return { version: HISTORY_VERSION, samples: [] };\n\t\t\tthis.report(\n\t\t\t\toperation,\n\t\t\t\t\"Could not read output-throughput history; the last valid snapshot was preserved\",\n\t\t\t\tcause,\n\t\t\t);\n\t\t\treturn undefined;\n\t\t}\n\t\ttry {\n\t\t\tconst value: unknown = JSON.parse(raw);\n\t\t\tif (!isHistoryDocument(value)) throw new Error(\"unsupported version or invalid schema\");\n\t\t\treturn { version: HISTORY_VERSION, samples: value.samples.map(normalizeSample) };\n\t\t} catch (cause) {\n\t\t\tthis.report(\n\t\t\t\t\"validate\",\n\t\t\t\t\"Output-throughput history is malformed or unsupported; original bytes and the last valid snapshot were preserved\",\n\t\t\t\tcause,\n\t\t\t);\n\t\t\treturn undefined;\n\t\t}\n\t}\n\n\tprivate async enqueue(operation: () => Promise<void>): Promise<void> {\n\t\ttry {\n\t\t\tif (!this.queueKey) this.queueKey = canonicalPath(this.path);\n\t\t\tconst queueKey = this.queueKey;\n\t\t\tlet key: string;\n\t\t\ttry {\n\t\t\t\tkey = await queueKey;\n\t\t\t} catch (cause) {\n\t\t\t\tif (this.queueKey === queueKey) this.queueKey = undefined;\n\t\t\t\tthrow cause;\n\t\t\t}\n\t\t\tconst previous = queues.get(key) ?? Promise.resolve();\n\t\t\tconst current = previous.catch(() => undefined).then(operation);\n\t\t\tqueues.set(key, current);\n\t\t\ttry {\n\t\t\t\tawait current;\n\t\t\t} finally {\n\t\t\t\tif (queues.get(key) === current) queues.delete(key);\n\t\t\t}\n\t\t} catch (cause) {\n\t\t\tthis.report(\"write\", \"Output-throughput history operation failed; existing history was preserved\", cause);\n\t\t}\n\t}\n\n\tprivate report(operation: OutputThroughputHistoryDiagnostic[\"operation\"], message: string, cause?: unknown): void {\n\t\tconst normalizedCause = cause instanceof Error ? cause.message : cause === undefined ? \"\" : String(cause);\n\t\tconst key = `${resolve(this.path)}\\0${operation}\\0${normalizedCause}`;\n\t\tif (this.reportedDiagnostics.has(key)) return;\n\t\tthis.reportedDiagnostics.add(key);\n\t\tthis.onDiagnostic?.({ operation, path: this.path, message, cause });\n\t}\n}\n\nasync function canonicalPath(path: string): Promise<string> {\n\tawait mkdir(dirname(path), { recursive: true });\n\tconst parent = await realpath(dirname(path));\n\treturn join(parent, basename(path));\n}\n\nfunction isHistoryDocument(value: unknown): value is HistoryDocument {\n\tif (!value || typeof value !== \"object\") return false;\n\tconst document = value as { version?: unknown; samples?: unknown };\n\treturn (\n\t\tdocument.version === HISTORY_VERSION &&\n\t\tArray.isArray(document.samples) &&\n\t\tdocument.samples.length <= MAX_HISTORY_SAMPLES &&\n\t\tdocument.samples.every(isValidSample)\n\t);\n}\n\nfunction isValidSample(value: unknown): value is OutputThroughputSample {\n\tif (!value || typeof value !== \"object\") return false;\n\tconst sample = value as Partial<OutputThroughputSample>;\n\treturn (\n\t\tisBoundedString(sample.provider) &&\n\t\tisBoundedString(sample.model) &&\n\t\t(sample.responseModel === undefined || isBoundedString(sample.responseModel)) &&\n\t\tisPositiveFinite(sample.outputTokens) &&\n\t\tisPositiveFinite(sample.durationMs) &&\n\t\tNumber.isFinite(sample.observedAt) &&\n\t\t(sample.observedAt ?? -1) >= 0\n\t);\n}\n\nfunction normalizeSample(sample: OutputThroughputSample): OutputThroughputSample {\n\treturn {\n\t\tprovider: sample.provider,\n\t\tmodel: sample.model,\n\t\t...(sample.responseModel === undefined ? {} : { responseModel: sample.responseModel }),\n\t\toutputTokens: sample.outputTokens,\n\t\tdurationMs: sample.durationMs,\n\t\tobservedAt: sample.observedAt,\n\t};\n}\n\nfunction isBoundedString(value: unknown): value is string {\n\treturn typeof value === \"string\" && value.length > 0 && value.length <= MAX_STRING_LENGTH;\n}\n\nfunction isPositiveFinite(value: unknown): value is number {\n\treturn typeof value === \"number\" && Number.isFinite(value) && value > 0;\n}\n\nfunction mergeSamples(\n\tsamples: readonly OutputThroughputSample[],\n\tadded: OutputThroughputSample,\n): OutputThroughputSample[] {\n\tconst unique = new Map<string, OutputThroughputSample>();\n\tfor (const sample of [...samples, added]) unique.set(sampleIdentity(sample), normalizeSample(sample));\n\tconst byModel = new Map<string, OutputThroughputSample[]>();\n\tfor (const sample of unique.values()) {\n\t\tconst key = `${sample.provider.length}:${sample.provider}${sample.model}`;\n\t\tconst group = byModel.get(key) ?? [];\n\t\tgroup.push(sample);\n\t\tbyModel.set(key, group);\n\t}\n\treturn [...byModel.values()]\n\t\t.flatMap((group) => group.sort((left, right) => left.observedAt - right.observedAt).slice(-MAX_SAMPLES_PER_MODEL))\n\t\t.sort((left, right) => left.observedAt - right.observedAt)\n\t\t.slice(-MAX_HISTORY_SAMPLES);\n}\n\nfunction sampleIdentity(sample: OutputThroughputSample): string {\n\treturn JSON.stringify([\n\t\tsample.provider,\n\t\tsample.model,\n\t\tsample.responseModel ?? null,\n\t\tsample.outputTokens,\n\t\tsample.durationMs,\n\t\tsample.observedAt,\n\t]);\n}\n\nfunction historyFromSamples(samples: readonly OutputThroughputSample[]): OutputThroughputHistory {\n\tconst history = new OutputThroughputHistory();\n\tfor (const sample of samples) history.add(sample);\n\treturn history;\n}\n\nfunction isMissingFile(cause: unknown): boolean {\n\treturn cause instanceof Error && \"code\" in cause && cause.code === \"ENOENT\";\n}\n"]}
@@ -0,0 +1,240 @@
1
+ import { randomUUID } from "node:crypto";
2
+ import { mkdir, open, readFile, realpath, rename, unlink, writeFile } from "node:fs/promises";
3
+ import { basename, dirname, join, resolve } from "node:path";
4
+ import lockfile from "proper-lockfile";
5
+ import { OutputThroughputHistory } from "./output-throughput.js";
6
+ // SCRAMJET-DIVERGENCE: Persist bounded, profile-scoped provider throughput across concurrent processes (#476).
7
+ const HISTORY_VERSION = 1;
8
+ const MAX_SAMPLES_PER_MODEL = 20;
9
+ const MAX_HISTORY_SAMPLES = 20000;
10
+ const MAX_STRING_LENGTH = 512;
11
+ const queues = new Map();
12
+ export class OutputThroughputHistoryStore {
13
+ path;
14
+ onDiagnostic;
15
+ history = new OutputThroughputHistory();
16
+ queueKey;
17
+ pending = Promise.resolve();
18
+ reportedDiagnostics = new Set();
19
+ constructor(path, onDiagnostic) {
20
+ this.path = path;
21
+ this.onDiagnostic = onDiagnostic;
22
+ }
23
+ snapshot() {
24
+ return historyFromSamples(this.history.allSamples());
25
+ }
26
+ async refresh() {
27
+ await this.enqueue(async () => {
28
+ const document = await this.readDocument("refresh");
29
+ if (document)
30
+ this.history = historyFromSamples(document.samples);
31
+ });
32
+ return this.snapshot();
33
+ }
34
+ submit(sample) {
35
+ if (!isValidSample(sample)) {
36
+ this.report("validate", "Invalid output-throughput sample was not persisted; existing history was preserved");
37
+ return;
38
+ }
39
+ const normalized = normalizeSample(sample);
40
+ this.pending = this.enqueue(() => this.update(normalized));
41
+ }
42
+ async flush() {
43
+ await this.pending;
44
+ }
45
+ async update(sample) {
46
+ await mkdir(dirname(this.path), { recursive: true });
47
+ const anchorPath = join(dirname(this.path), `${basename(this.path, ".json")}.lock`);
48
+ try {
49
+ await writeFile(anchorPath, "", { flag: "a" });
50
+ }
51
+ catch (cause) {
52
+ this.report("lock", "Could not prepare the throughput-history lock; existing history was preserved", cause);
53
+ return;
54
+ }
55
+ let release;
56
+ try {
57
+ release = await lockfile.lock(anchorPath, {
58
+ realpath: false,
59
+ retries: { retries: 8, factor: 2, minTimeout: 25, maxTimeout: 1000, randomize: true },
60
+ stale: 30000,
61
+ });
62
+ const current = await this.readDocument("read");
63
+ if (!current)
64
+ return;
65
+ const samples = mergeSamples(current.samples, sample);
66
+ const document = { version: HISTORY_VERSION, samples };
67
+ const temporaryPath = join(dirname(this.path), `.${basename(this.path)}.${process.pid}.${randomUUID()}.tmp`);
68
+ let temporaryCreated = false;
69
+ try {
70
+ const handle = await open(temporaryPath, "wx", 0o600);
71
+ temporaryCreated = true;
72
+ try {
73
+ await handle.writeFile(`${JSON.stringify(document, null, 2)}\n`, "utf8");
74
+ await handle.sync();
75
+ }
76
+ finally {
77
+ await handle.close();
78
+ }
79
+ await rename(temporaryPath, this.path);
80
+ temporaryCreated = false;
81
+ this.history = historyFromSamples(samples);
82
+ }
83
+ catch (cause) {
84
+ this.report("write", "Could not commit output-throughput history; existing history was preserved", cause);
85
+ }
86
+ finally {
87
+ if (temporaryCreated) {
88
+ await unlink(temporaryPath).catch((cause) => {
89
+ this.report("unlink", "Could not remove the temporary output-throughput history file", cause);
90
+ });
91
+ }
92
+ }
93
+ }
94
+ catch (cause) {
95
+ this.report("lock", "Could not lock output-throughput history; existing history was preserved", cause);
96
+ }
97
+ finally {
98
+ await release?.().catch((cause) => {
99
+ this.report("unlock", "Could not release the output-throughput history lock", cause);
100
+ });
101
+ }
102
+ }
103
+ async readDocument(operation) {
104
+ let raw;
105
+ try {
106
+ raw = await readFile(this.path, "utf8");
107
+ }
108
+ catch (cause) {
109
+ if (isMissingFile(cause))
110
+ return { version: HISTORY_VERSION, samples: [] };
111
+ this.report(operation, "Could not read output-throughput history; the last valid snapshot was preserved", cause);
112
+ return undefined;
113
+ }
114
+ try {
115
+ const value = JSON.parse(raw);
116
+ if (!isHistoryDocument(value))
117
+ throw new Error("unsupported version or invalid schema");
118
+ return { version: HISTORY_VERSION, samples: value.samples.map(normalizeSample) };
119
+ }
120
+ catch (cause) {
121
+ this.report("validate", "Output-throughput history is malformed or unsupported; original bytes and the last valid snapshot were preserved", cause);
122
+ return undefined;
123
+ }
124
+ }
125
+ async enqueue(operation) {
126
+ try {
127
+ if (!this.queueKey)
128
+ this.queueKey = canonicalPath(this.path);
129
+ const queueKey = this.queueKey;
130
+ let key;
131
+ try {
132
+ key = await queueKey;
133
+ }
134
+ catch (cause) {
135
+ if (this.queueKey === queueKey)
136
+ this.queueKey = undefined;
137
+ throw cause;
138
+ }
139
+ const previous = queues.get(key) ?? Promise.resolve();
140
+ const current = previous.catch(() => undefined).then(operation);
141
+ queues.set(key, current);
142
+ try {
143
+ await current;
144
+ }
145
+ finally {
146
+ if (queues.get(key) === current)
147
+ queues.delete(key);
148
+ }
149
+ }
150
+ catch (cause) {
151
+ this.report("write", "Output-throughput history operation failed; existing history was preserved", cause);
152
+ }
153
+ }
154
+ report(operation, message, cause) {
155
+ const normalizedCause = cause instanceof Error ? cause.message : cause === undefined ? "" : String(cause);
156
+ const key = `${resolve(this.path)}\0${operation}\0${normalizedCause}`;
157
+ if (this.reportedDiagnostics.has(key))
158
+ return;
159
+ this.reportedDiagnostics.add(key);
160
+ this.onDiagnostic?.({ operation, path: this.path, message, cause });
161
+ }
162
+ }
163
+ async function canonicalPath(path) {
164
+ await mkdir(dirname(path), { recursive: true });
165
+ const parent = await realpath(dirname(path));
166
+ return join(parent, basename(path));
167
+ }
168
+ function isHistoryDocument(value) {
169
+ if (!value || typeof value !== "object")
170
+ return false;
171
+ const document = value;
172
+ return (document.version === HISTORY_VERSION &&
173
+ Array.isArray(document.samples) &&
174
+ document.samples.length <= MAX_HISTORY_SAMPLES &&
175
+ document.samples.every(isValidSample));
176
+ }
177
+ function isValidSample(value) {
178
+ if (!value || typeof value !== "object")
179
+ return false;
180
+ const sample = value;
181
+ return (isBoundedString(sample.provider) &&
182
+ isBoundedString(sample.model) &&
183
+ (sample.responseModel === undefined || isBoundedString(sample.responseModel)) &&
184
+ isPositiveFinite(sample.outputTokens) &&
185
+ isPositiveFinite(sample.durationMs) &&
186
+ Number.isFinite(sample.observedAt) &&
187
+ (sample.observedAt ?? -1) >= 0);
188
+ }
189
+ function normalizeSample(sample) {
190
+ return {
191
+ provider: sample.provider,
192
+ model: sample.model,
193
+ ...(sample.responseModel === undefined ? {} : { responseModel: sample.responseModel }),
194
+ outputTokens: sample.outputTokens,
195
+ durationMs: sample.durationMs,
196
+ observedAt: sample.observedAt,
197
+ };
198
+ }
199
+ function isBoundedString(value) {
200
+ return typeof value === "string" && value.length > 0 && value.length <= MAX_STRING_LENGTH;
201
+ }
202
+ function isPositiveFinite(value) {
203
+ return typeof value === "number" && Number.isFinite(value) && value > 0;
204
+ }
205
+ function mergeSamples(samples, added) {
206
+ const unique = new Map();
207
+ for (const sample of [...samples, added])
208
+ unique.set(sampleIdentity(sample), normalizeSample(sample));
209
+ const byModel = new Map();
210
+ for (const sample of unique.values()) {
211
+ const key = `${sample.provider.length}:${sample.provider}${sample.model}`;
212
+ const group = byModel.get(key) ?? [];
213
+ group.push(sample);
214
+ byModel.set(key, group);
215
+ }
216
+ return [...byModel.values()]
217
+ .flatMap((group) => group.sort((left, right) => left.observedAt - right.observedAt).slice(-MAX_SAMPLES_PER_MODEL))
218
+ .sort((left, right) => left.observedAt - right.observedAt)
219
+ .slice(-MAX_HISTORY_SAMPLES);
220
+ }
221
+ function sampleIdentity(sample) {
222
+ return JSON.stringify([
223
+ sample.provider,
224
+ sample.model,
225
+ sample.responseModel ?? null,
226
+ sample.outputTokens,
227
+ sample.durationMs,
228
+ sample.observedAt,
229
+ ]);
230
+ }
231
+ function historyFromSamples(samples) {
232
+ const history = new OutputThroughputHistory();
233
+ for (const sample of samples)
234
+ history.add(sample);
235
+ return history;
236
+ }
237
+ function isMissingFile(cause) {
238
+ return cause instanceof Error && "code" in cause && cause.code === "ENOENT";
239
+ }
240
+ //# sourceMappingURL=output-throughput-history.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"output-throughput-history.js","sourceRoot":"","sources":["../../src/core/output-throughput-history.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,MAAM,aAAa,CAAC;AACzC,OAAO,EAAE,KAAK,EAAE,IAAI,EAAE,QAAQ,EAAE,QAAQ,EAAE,MAAM,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,kBAAkB,CAAC;AAC9F,OAAO,EAAE,QAAQ,EAAE,OAAO,EAAE,IAAI,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AAC7D,OAAO,QAAQ,MAAM,iBAAiB,CAAC;AACvC,OAAO,EAAE,uBAAuB,EAA+B,MAAM,wBAAwB,CAAC;AAE9F,+GAA+G;AAE/G,MAAM,eAAe,GAAG,CAAC,CAAC;AAC1B,MAAM,qBAAqB,GAAG,EAAE,CAAC;AACjC,MAAM,mBAAmB,GAAG,KAAK,CAAC;AAClC,MAAM,iBAAiB,GAAG,GAAG,CAAC;AAC9B,MAAM,MAAM,GAAG,IAAI,GAAG,EAAyB,CAAC;AAchD,MAAM,OAAO,4BAA4B;IAO9B,IAAI;IACI,YAAY;IAPtB,OAAO,GAAG,IAAI,uBAAuB,EAAE,CAAC;IACxC,QAAQ,CAAmB;IAC3B,OAAO,GAAkB,OAAO,CAAC,OAAO,EAAE,CAAC;IAClC,mBAAmB,GAAG,IAAI,GAAG,EAAU,CAAC;IAEzD,YACU,IAAY,EACJ,YAAsE,EACtF;oBAFQ,IAAI;4BACI,YAAY;IAC3B,CAAC;IAEJ,QAAQ,GAA4B;QACnC,OAAO,kBAAkB,CAAC,IAAI,CAAC,OAAO,CAAC,UAAU,EAAE,CAAC,CAAC;IAAA,CACrD;IAED,KAAK,CAAC,OAAO,GAAqC;QACjD,MAAM,IAAI,CAAC,OAAO,CAAC,KAAK,IAAI,EAAE,CAAC;YAC9B,MAAM,QAAQ,GAAG,MAAM,IAAI,CAAC,YAAY,CAAC,SAAS,CAAC,CAAC;YACpD,IAAI,QAAQ;gBAAE,IAAI,CAAC,OAAO,GAAG,kBAAkB,CAAC,QAAQ,CAAC,OAAO,CAAC,CAAC;QAAA,CAClE,CAAC,CAAC;QACH,OAAO,IAAI,CAAC,QAAQ,EAAE,CAAC;IAAA,CACvB;IAED,MAAM,CAAC,MAA8B,EAAQ;QAC5C,IAAI,CAAC,aAAa,CAAC,MAAM,CAAC,EAAE,CAAC;YAC5B,IAAI,CAAC,MAAM,CAAC,UAAU,EAAE,oFAAoF,CAAC,CAAC;YAC9G,OAAO;QACR,CAAC;QACD,MAAM,UAAU,GAAG,eAAe,CAAC,MAAM,CAAC,CAAC;QAC3C,IAAI,CAAC,OAAO,GAAG,IAAI,CAAC,OAAO,CAAC,GAAG,EAAE,CAAC,IAAI,CAAC,MAAM,CAAC,UAAU,CAAC,CAAC,CAAC;IAAA,CAC3D;IAED,KAAK,CAAC,KAAK,GAAkB;QAC5B,MAAM,IAAI,CAAC,OAAO,CAAC;IAAA,CACnB;IAEO,KAAK,CAAC,MAAM,CAAC,MAA8B,EAAiB;QACnE,MAAM,KAAK,CAAC,OAAO,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CAAC;QACrD,MAAM,UAAU,GAAG,IAAI,CAAC,OAAO,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE,GAAG,QAAQ,CAAC,IAAI,CAAC,IAAI,EAAE,OAAO,CAAC,OAAO,CAAC,CAAC;QACpF,IAAI,CAAC;YACJ,MAAM,SAAS,CAAC,UAAU,EAAE,EAAE,EAAE,EAAE,IAAI,EAAE,GAAG,EAAE,CAAC,CAAC;QAChD,CAAC;QAAC,OAAO,KAAK,EAAE,CAAC;YAChB,IAAI,CAAC,MAAM,CAAC,MAAM,EAAE,+EAA+E,EAAE,KAAK,CAAC,CAAC;YAC5G,OAAO;QACR,CAAC;QAED,IAAI,OAA0C,CAAC;QAC/C,IAAI,CAAC;YACJ,OAAO,GAAG,MAAM,QAAQ,CAAC,IAAI,CAAC,UAAU,EAAE;gBACzC,QAAQ,EAAE,KAAK;gBACf,OAAO,EAAE,EAAE,OAAO,EAAE,CAAC,EAAE,MAAM,EAAE,CAAC,EAAE,UAAU,EAAE,EAAE,EAAE,UAAU,EAAE,IAAI,EAAE,SAAS,EAAE,IAAI,EAAE;gBACrF,KAAK,EAAE,KAAK;aACZ,CAAC,CAAC;YACH,MAAM,OAAO,GAAG,MAAM,IAAI,CAAC,YAAY,CAAC,MAAM,CAAC,CAAC;YAChD,IAAI,CAAC,OAAO;gBAAE,OAAO;YACrB,MAAM,OAAO,GAAG,YAAY,CAAC,OAAO,CAAC,OAAO,EAAE,MAAM,CAAC,CAAC;YACtD,MAAM,QAAQ,GAAoB,EAAE,OAAO,EAAE,eAAe,EAAE,OAAO,EAAE,CAAC;YACxE,MAAM,aAAa,GAAG,IAAI,CAAC,OAAO,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE,IAAI,QAAQ,CAAC,IAAI,CAAC,IAAI,CAAC,IAAI,OAAO,CAAC,GAAG,IAAI,UAAU,EAAE,MAAM,CAAC,CAAC;YAC7G,IAAI,gBAAgB,GAAG,KAAK,CAAC;YAC7B,IAAI,CAAC;gBACJ,MAAM,MAAM,GAAG,MAAM,IAAI,CAAC,aAAa,EAAE,IAAI,EAAE,KAAK,CAAC,CAAC;gBACtD,gBAAgB,GAAG,IAAI,CAAC;gBACxB,IAAI,CAAC;oBACJ,MAAM,MAAM,CAAC,SAAS,CAAC,GAAG,IAAI,CAAC,SAAS,CAAC,QAAQ,EAAE,IAAI,EAAE,CAAC,CAAC,IAAI,EAAE,MAAM,CAAC,CAAC;oBACzE,MAAM,MAAM,CAAC,IAAI,EAAE,CAAC;gBACrB,CAAC;wBAAS,CAAC;oBACV,MAAM,MAAM,CAAC,KAAK,EAAE,CAAC;gBACtB,CAAC;gBACD,MAAM,MAAM,CAAC,aAAa,EAAE,IAAI,CAAC,IAAI,CAAC,CAAC;gBACvC,gBAAgB,GAAG,KAAK,CAAC;gBACzB,IAAI,CAAC,OAAO,GAAG,kBAAkB,CAAC,OAAO,CAAC,CAAC;YAC5C,CAAC;YAAC,OAAO,KAAK,EAAE,CAAC;gBAChB,IAAI,CAAC,MAAM,CAAC,OAAO,EAAE,4EAA4E,EAAE,KAAK,CAAC,CAAC;YAC3G,CAAC;oBAAS,CAAC;gBACV,IAAI,gBAAgB,EAAE,CAAC;oBACtB,MAAM,MAAM,CAAC,aAAa,CAAC,CAAC,KAAK,CAAC,CAAC,KAAK,EAAE,EAAE,CAAC;wBAC5C,IAAI,CAAC,MAAM,CAAC,QAAQ,EAAE,+DAA+D,EAAE,KAAK,CAAC,CAAC;oBAAA,CAC9F,CAAC,CAAC;gBACJ,CAAC;YACF,CAAC;QACF,CAAC;QAAC,OAAO,KAAK,EAAE,CAAC;YAChB,IAAI,CAAC,MAAM,CAAC,MAAM,EAAE,0EAA0E,EAAE,KAAK,CAAC,CAAC;QACxG,CAAC;gBAAS,CAAC;YACV,MAAM,OAAO,EAAE,EAAE,CAAC,KAAK,CAAC,CAAC,KAAK,EAAE,EAAE,CAAC;gBAClC,IAAI,CAAC,MAAM,CAAC,QAAQ,EAAE,sDAAsD,EAAE,KAAK,CAAC,CAAC;YAAA,CACrF,CAAC,CAAC;QACJ,CAAC;IAAA,CACD;IAEO,KAAK,CAAC,YAAY,CAAC,SAA6B,EAAwC;QAC/F,IAAI,GAAW,CAAC;QAChB,IAAI,CAAC;YACJ,GAAG,GAAG,MAAM,QAAQ,CAAC,IAAI,CAAC,IAAI,EAAE,MAAM,CAAC,CAAC;QACzC,CAAC;QAAC,OAAO,KAAK,EAAE,CAAC;YAChB,IAAI,aAAa,CAAC,KAAK,CAAC;gBAAE,OAAO,EAAE,OAAO,EAAE,eAAe,EAAE,OAAO,EAAE,EAAE,EAAE,CAAC;YAC3E,IAAI,CAAC,MAAM,CACV,SAAS,EACT,iFAAiF,EACjF,KAAK,CACL,CAAC;YACF,OAAO,SAAS,CAAC;QAClB,CAAC;QACD,IAAI,CAAC;YACJ,MAAM,KAAK,GAAY,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC;YACvC,IAAI,CAAC,iBAAiB,CAAC,KAAK,CAAC;gBAAE,MAAM,IAAI,KAAK,CAAC,uCAAuC,CAAC,CAAC;YACxF,OAAO,EAAE,OAAO,EAAE,eAAe,EAAE,OAAO,EAAE,KAAK,CAAC,OAAO,CAAC,GAAG,CAAC,eAAe,CAAC,EAAE,CAAC;QAClF,CAAC;QAAC,OAAO,KAAK,EAAE,CAAC;YAChB,IAAI,CAAC,MAAM,CACV,UAAU,EACV,kHAAkH,EAClH,KAAK,CACL,CAAC;YACF,OAAO,SAAS,CAAC;QAClB,CAAC;IAAA,CACD;IAEO,KAAK,CAAC,OAAO,CAAC,SAA8B,EAAiB;QACpE,IAAI,CAAC;YACJ,IAAI,CAAC,IAAI,CAAC,QAAQ;gBAAE,IAAI,CAAC,QAAQ,GAAG,aAAa,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;YAC7D,MAAM,QAAQ,GAAG,IAAI,CAAC,QAAQ,CAAC;YAC/B,IAAI,GAAW,CAAC;YAChB,IAAI,CAAC;gBACJ,GAAG,GAAG,MAAM,QAAQ,CAAC;YACtB,CAAC;YAAC,OAAO,KAAK,EAAE,CAAC;gBAChB,IAAI,IAAI,CAAC,QAAQ,KAAK,QAAQ;oBAAE,IAAI,CAAC,QAAQ,GAAG,SAAS,CAAC;gBAC1D,MAAM,KAAK,CAAC;YACb,CAAC;YACD,MAAM,QAAQ,GAAG,MAAM,CAAC,GAAG,CAAC,GAAG,CAAC,IAAI,OAAO,CAAC,OAAO,EAAE,CAAC;YACtD,MAAM,OAAO,GAAG,QAAQ,CAAC,KAAK,CAAC,GAAG,EAAE,CAAC,SAAS,CAAC,CAAC,IAAI,CAAC,SAAS,CAAC,CAAC;YAChE,MAAM,CAAC,GAAG,CAAC,GAAG,EAAE,OAAO,CAAC,CAAC;YACzB,IAAI,CAAC;gBACJ,MAAM,OAAO,CAAC;YACf,CAAC;oBAAS,CAAC;gBACV,IAAI,MAAM,CAAC,GAAG,CAAC,GAAG,CAAC,KAAK,OAAO;oBAAE,MAAM,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC;YACrD,CAAC;QACF,CAAC;QAAC,OAAO,KAAK,EAAE,CAAC;YAChB,IAAI,CAAC,MAAM,CAAC,OAAO,EAAE,4EAA4E,EAAE,KAAK,CAAC,CAAC;QAC3G,CAAC;IAAA,CACD;IAEO,MAAM,CAAC,SAAyD,EAAE,OAAe,EAAE,KAAe,EAAQ;QACjH,MAAM,eAAe,GAAG,KAAK,YAAY,KAAK,CAAC,CAAC,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,KAAK,KAAK,SAAS,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC;QAC1G,MAAM,GAAG,GAAG,GAAG,OAAO,CAAC,IAAI,CAAC,IAAI,CAAC,KAAK,SAAS,KAAK,eAAe,EAAE,CAAC;QACtE,IAAI,IAAI,CAAC,mBAAmB,CAAC,GAAG,CAAC,GAAG,CAAC;YAAE,OAAO;QAC9C,IAAI,CAAC,mBAAmB,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC;QAClC,IAAI,CAAC,YAAY,EAAE,CAAC,EAAE,SAAS,EAAE,IAAI,EAAE,IAAI,CAAC,IAAI,EAAE,OAAO,EAAE,KAAK,EAAE,CAAC,CAAC;IAAA,CACpE;CACD;AAED,KAAK,UAAU,aAAa,CAAC,IAAY,EAAmB;IAC3D,MAAM,KAAK,CAAC,OAAO,CAAC,IAAI,CAAC,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CAAC;IAChD,MAAM,MAAM,GAAG,MAAM,QAAQ,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC;IAC7C,OAAO,IAAI,CAAC,MAAM,EAAE,QAAQ,CAAC,IAAI,CAAC,CAAC,CAAC;AAAA,CACpC;AAED,SAAS,iBAAiB,CAAC,KAAc,EAA4B;IACpE,IAAI,CAAC,KAAK,IAAI,OAAO,KAAK,KAAK,QAAQ;QAAE,OAAO,KAAK,CAAC;IACtD,MAAM,QAAQ,GAAG,KAAiD,CAAC;IACnE,OAAO,CACN,QAAQ,CAAC,OAAO,KAAK,eAAe;QACpC,KAAK,CAAC,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAC;QAC/B,QAAQ,CAAC,OAAO,CAAC,MAAM,IAAI,mBAAmB;QAC9C,QAAQ,CAAC,OAAO,CAAC,KAAK,CAAC,aAAa,CAAC,CACrC,CAAC;AAAA,CACF;AAED,SAAS,aAAa,CAAC,KAAc,EAAmC;IACvE,IAAI,CAAC,KAAK,IAAI,OAAO,KAAK,KAAK,QAAQ;QAAE,OAAO,KAAK,CAAC;IACtD,MAAM,MAAM,GAAG,KAAwC,CAAC;IACxD,OAAO,CACN,eAAe,CAAC,MAAM,CAAC,QAAQ,CAAC;QAChC,eAAe,CAAC,MAAM,CAAC,KAAK,CAAC;QAC7B,CAAC,MAAM,CAAC,aAAa,KAAK,SAAS,IAAI,eAAe,CAAC,MAAM,CAAC,aAAa,CAAC,CAAC;QAC7E,gBAAgB,CAAC,MAAM,CAAC,YAAY,CAAC;QACrC,gBAAgB,CAAC,MAAM,CAAC,UAAU,CAAC;QACnC,MAAM,CAAC,QAAQ,CAAC,MAAM,CAAC,UAAU,CAAC;QAClC,CAAC,MAAM,CAAC,UAAU,IAAI,CAAC,CAAC,CAAC,IAAI,CAAC,CAC9B,CAAC;AAAA,CACF;AAED,SAAS,eAAe,CAAC,MAA8B,EAA0B;IAChF,OAAO;QACN,QAAQ,EAAE,MAAM,CAAC,QAAQ;QACzB,KAAK,EAAE,MAAM,CAAC,KAAK;QACnB,GAAG,CAAC,MAAM,CAAC,aAAa,KAAK,SAAS,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,EAAE,aAAa,EAAE,MAAM,CAAC,aAAa,EAAE,CAAC;QACtF,YAAY,EAAE,MAAM,CAAC,YAAY;QACjC,UAAU,EAAE,MAAM,CAAC,UAAU;QAC7B,UAAU,EAAE,MAAM,CAAC,UAAU;KAC7B,CAAC;AAAA,CACF;AAED,SAAS,eAAe,CAAC,KAAc,EAAmB;IACzD,OAAO,OAAO,KAAK,KAAK,QAAQ,IAAI,KAAK,CAAC,MAAM,GAAG,CAAC,IAAI,KAAK,CAAC,MAAM,IAAI,iBAAiB,CAAC;AAAA,CAC1F;AAED,SAAS,gBAAgB,CAAC,KAAc,EAAmB;IAC1D,OAAO,OAAO,KAAK,KAAK,QAAQ,IAAI,MAAM,CAAC,QAAQ,CAAC,KAAK,CAAC,IAAI,KAAK,GAAG,CAAC,CAAC;AAAA,CACxE;AAED,SAAS,YAAY,CACpB,OAA0C,EAC1C,KAA6B,EACF;IAC3B,MAAM,MAAM,GAAG,IAAI,GAAG,EAAkC,CAAC;IACzD,KAAK,MAAM,MAAM,IAAI,CAAC,GAAG,OAAO,EAAE,KAAK,CAAC;QAAE,MAAM,CAAC,GAAG,CAAC,cAAc,CAAC,MAAM,CAAC,EAAE,eAAe,CAAC,MAAM,CAAC,CAAC,CAAC;IACtG,MAAM,OAAO,GAAG,IAAI,GAAG,EAAoC,CAAC;IAC5D,KAAK,MAAM,MAAM,IAAI,MAAM,CAAC,MAAM,EAAE,EAAE,CAAC;QACtC,MAAM,GAAG,GAAG,GAAG,MAAM,CAAC,QAAQ,CAAC,MAAM,IAAI,MAAM,CAAC,QAAQ,GAAG,MAAM,CAAC,KAAK,EAAE,CAAC;QAC1E,MAAM,KAAK,GAAG,OAAO,CAAC,GAAG,CAAC,GAAG,CAAC,IAAI,EAAE,CAAC;QACrC,KAAK,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC;QACnB,OAAO,CAAC,GAAG,CAAC,GAAG,EAAE,KAAK,CAAC,CAAC;IACzB,CAAC;IACD,OAAO,CAAC,GAAG,OAAO,CAAC,MAAM,EAAE,CAAC;SAC1B,OAAO,CAAC,CAAC,KAAK,EAAE,EAAE,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC,IAAI,EAAE,KAAK,EAAE,EAAE,CAAC,IAAI,CAAC,UAAU,GAAG,KAAK,CAAC,UAAU,CAAC,CAAC,KAAK,CAAC,CAAC,qBAAqB,CAAC,CAAC;SACjH,IAAI,CAAC,CAAC,IAAI,EAAE,KAAK,EAAE,EAAE,CAAC,IAAI,CAAC,UAAU,GAAG,KAAK,CAAC,UAAU,CAAC;SACzD,KAAK,CAAC,CAAC,mBAAmB,CAAC,CAAC;AAAA,CAC9B;AAED,SAAS,cAAc,CAAC,MAA8B,EAAU;IAC/D,OAAO,IAAI,CAAC,SAAS,CAAC;QACrB,MAAM,CAAC,QAAQ;QACf,MAAM,CAAC,KAAK;QACZ,MAAM,CAAC,aAAa,IAAI,IAAI;QAC5B,MAAM,CAAC,YAAY;QACnB,MAAM,CAAC,UAAU;QACjB,MAAM,CAAC,UAAU;KACjB,CAAC,CAAC;AAAA,CACH;AAED,SAAS,kBAAkB,CAAC,OAA0C,EAA2B;IAChG,MAAM,OAAO,GAAG,IAAI,uBAAuB,EAAE,CAAC;IAC9C,KAAK,MAAM,MAAM,IAAI,OAAO;QAAE,OAAO,CAAC,GAAG,CAAC,MAAM,CAAC,CAAC;IAClD,OAAO,OAAO,CAAC;AAAA,CACf;AAED,SAAS,aAAa,CAAC,KAAc,EAAW;IAC/C,OAAO,KAAK,YAAY,KAAK,IAAI,MAAM,IAAI,KAAK,IAAI,KAAK,CAAC,IAAI,KAAK,QAAQ,CAAC;AAAA,CAC5E","sourcesContent":["import { randomUUID } from \"node:crypto\";\nimport { mkdir, open, readFile, realpath, rename, unlink, writeFile } from \"node:fs/promises\";\nimport { basename, dirname, join, resolve } from \"node:path\";\nimport lockfile from \"proper-lockfile\";\nimport { OutputThroughputHistory, type OutputThroughputSample } from \"./output-throughput.js\";\n\n// SCRAMJET-DIVERGENCE: Persist bounded, profile-scoped provider throughput across concurrent processes (#476).\n\nconst HISTORY_VERSION = 1;\nconst MAX_SAMPLES_PER_MODEL = 20;\nconst MAX_HISTORY_SAMPLES = 20000;\nconst MAX_STRING_LENGTH = 512;\nconst queues = new Map<string, Promise<void>>();\n\nexport interface OutputThroughputHistoryDiagnostic {\n\toperation: \"refresh\" | \"lock\" | \"read\" | \"validate\" | \"write\" | \"rename\" | \"unlink\" | \"unlock\";\n\tpath: string;\n\tmessage: string;\n\tcause?: unknown;\n}\n\ninterface HistoryDocument {\n\tversion: 1;\n\tsamples: OutputThroughputSample[];\n}\n\nexport class OutputThroughputHistoryStore {\n\tprivate history = new OutputThroughputHistory();\n\tprivate queueKey?: Promise<string>;\n\tprivate pending: Promise<void> = Promise.resolve();\n\tprivate readonly reportedDiagnostics = new Set<string>();\n\n\tconstructor(\n\t\treadonly path: string,\n\t\tprivate readonly onDiagnostic?: (diagnostic: OutputThroughputHistoryDiagnostic) => void,\n\t) {}\n\n\tsnapshot(): OutputThroughputHistory {\n\t\treturn historyFromSamples(this.history.allSamples());\n\t}\n\n\tasync refresh(): Promise<OutputThroughputHistory> {\n\t\tawait this.enqueue(async () => {\n\t\t\tconst document = await this.readDocument(\"refresh\");\n\t\t\tif (document) this.history = historyFromSamples(document.samples);\n\t\t});\n\t\treturn this.snapshot();\n\t}\n\n\tsubmit(sample: OutputThroughputSample): void {\n\t\tif (!isValidSample(sample)) {\n\t\t\tthis.report(\"validate\", \"Invalid output-throughput sample was not persisted; existing history was preserved\");\n\t\t\treturn;\n\t\t}\n\t\tconst normalized = normalizeSample(sample);\n\t\tthis.pending = this.enqueue(() => this.update(normalized));\n\t}\n\n\tasync flush(): Promise<void> {\n\t\tawait this.pending;\n\t}\n\n\tprivate async update(sample: OutputThroughputSample): Promise<void> {\n\t\tawait mkdir(dirname(this.path), { recursive: true });\n\t\tconst anchorPath = join(dirname(this.path), `${basename(this.path, \".json\")}.lock`);\n\t\ttry {\n\t\t\tawait writeFile(anchorPath, \"\", { flag: \"a\" });\n\t\t} catch (cause) {\n\t\t\tthis.report(\"lock\", \"Could not prepare the throughput-history lock; existing history was preserved\", cause);\n\t\t\treturn;\n\t\t}\n\n\t\tlet release: (() => Promise<void>) | undefined;\n\t\ttry {\n\t\t\trelease = await lockfile.lock(anchorPath, {\n\t\t\t\trealpath: false,\n\t\t\t\tretries: { retries: 8, factor: 2, minTimeout: 25, maxTimeout: 1000, randomize: true },\n\t\t\t\tstale: 30000,\n\t\t\t});\n\t\t\tconst current = await this.readDocument(\"read\");\n\t\t\tif (!current) return;\n\t\t\tconst samples = mergeSamples(current.samples, sample);\n\t\t\tconst document: HistoryDocument = { version: HISTORY_VERSION, samples };\n\t\t\tconst temporaryPath = join(dirname(this.path), `.${basename(this.path)}.${process.pid}.${randomUUID()}.tmp`);\n\t\t\tlet temporaryCreated = false;\n\t\t\ttry {\n\t\t\t\tconst handle = await open(temporaryPath, \"wx\", 0o600);\n\t\t\t\ttemporaryCreated = true;\n\t\t\t\ttry {\n\t\t\t\t\tawait handle.writeFile(`${JSON.stringify(document, null, 2)}\\n`, \"utf8\");\n\t\t\t\t\tawait handle.sync();\n\t\t\t\t} finally {\n\t\t\t\t\tawait handle.close();\n\t\t\t\t}\n\t\t\t\tawait rename(temporaryPath, this.path);\n\t\t\t\ttemporaryCreated = false;\n\t\t\t\tthis.history = historyFromSamples(samples);\n\t\t\t} catch (cause) {\n\t\t\t\tthis.report(\"write\", \"Could not commit output-throughput history; existing history was preserved\", cause);\n\t\t\t} finally {\n\t\t\t\tif (temporaryCreated) {\n\t\t\t\t\tawait unlink(temporaryPath).catch((cause) => {\n\t\t\t\t\t\tthis.report(\"unlink\", \"Could not remove the temporary output-throughput history file\", cause);\n\t\t\t\t\t});\n\t\t\t\t}\n\t\t\t}\n\t\t} catch (cause) {\n\t\t\tthis.report(\"lock\", \"Could not lock output-throughput history; existing history was preserved\", cause);\n\t\t} finally {\n\t\t\tawait release?.().catch((cause) => {\n\t\t\t\tthis.report(\"unlock\", \"Could not release the output-throughput history lock\", cause);\n\t\t\t});\n\t\t}\n\t}\n\n\tprivate async readDocument(operation: \"refresh\" | \"read\"): Promise<HistoryDocument | undefined> {\n\t\tlet raw: string;\n\t\ttry {\n\t\t\traw = await readFile(this.path, \"utf8\");\n\t\t} catch (cause) {\n\t\t\tif (isMissingFile(cause)) return { version: HISTORY_VERSION, samples: [] };\n\t\t\tthis.report(\n\t\t\t\toperation,\n\t\t\t\t\"Could not read output-throughput history; the last valid snapshot was preserved\",\n\t\t\t\tcause,\n\t\t\t);\n\t\t\treturn undefined;\n\t\t}\n\t\ttry {\n\t\t\tconst value: unknown = JSON.parse(raw);\n\t\t\tif (!isHistoryDocument(value)) throw new Error(\"unsupported version or invalid schema\");\n\t\t\treturn { version: HISTORY_VERSION, samples: value.samples.map(normalizeSample) };\n\t\t} catch (cause) {\n\t\t\tthis.report(\n\t\t\t\t\"validate\",\n\t\t\t\t\"Output-throughput history is malformed or unsupported; original bytes and the last valid snapshot were preserved\",\n\t\t\t\tcause,\n\t\t\t);\n\t\t\treturn undefined;\n\t\t}\n\t}\n\n\tprivate async enqueue(operation: () => Promise<void>): Promise<void> {\n\t\ttry {\n\t\t\tif (!this.queueKey) this.queueKey = canonicalPath(this.path);\n\t\t\tconst queueKey = this.queueKey;\n\t\t\tlet key: string;\n\t\t\ttry {\n\t\t\t\tkey = await queueKey;\n\t\t\t} catch (cause) {\n\t\t\t\tif (this.queueKey === queueKey) this.queueKey = undefined;\n\t\t\t\tthrow cause;\n\t\t\t}\n\t\t\tconst previous = queues.get(key) ?? Promise.resolve();\n\t\t\tconst current = previous.catch(() => undefined).then(operation);\n\t\t\tqueues.set(key, current);\n\t\t\ttry {\n\t\t\t\tawait current;\n\t\t\t} finally {\n\t\t\t\tif (queues.get(key) === current) queues.delete(key);\n\t\t\t}\n\t\t} catch (cause) {\n\t\t\tthis.report(\"write\", \"Output-throughput history operation failed; existing history was preserved\", cause);\n\t\t}\n\t}\n\n\tprivate report(operation: OutputThroughputHistoryDiagnostic[\"operation\"], message: string, cause?: unknown): void {\n\t\tconst normalizedCause = cause instanceof Error ? cause.message : cause === undefined ? \"\" : String(cause);\n\t\tconst key = `${resolve(this.path)}\\0${operation}\\0${normalizedCause}`;\n\t\tif (this.reportedDiagnostics.has(key)) return;\n\t\tthis.reportedDiagnostics.add(key);\n\t\tthis.onDiagnostic?.({ operation, path: this.path, message, cause });\n\t}\n}\n\nasync function canonicalPath(path: string): Promise<string> {\n\tawait mkdir(dirname(path), { recursive: true });\n\tconst parent = await realpath(dirname(path));\n\treturn join(parent, basename(path));\n}\n\nfunction isHistoryDocument(value: unknown): value is HistoryDocument {\n\tif (!value || typeof value !== \"object\") return false;\n\tconst document = value as { version?: unknown; samples?: unknown };\n\treturn (\n\t\tdocument.version === HISTORY_VERSION &&\n\t\tArray.isArray(document.samples) &&\n\t\tdocument.samples.length <= MAX_HISTORY_SAMPLES &&\n\t\tdocument.samples.every(isValidSample)\n\t);\n}\n\nfunction isValidSample(value: unknown): value is OutputThroughputSample {\n\tif (!value || typeof value !== \"object\") return false;\n\tconst sample = value as Partial<OutputThroughputSample>;\n\treturn (\n\t\tisBoundedString(sample.provider) &&\n\t\tisBoundedString(sample.model) &&\n\t\t(sample.responseModel === undefined || isBoundedString(sample.responseModel)) &&\n\t\tisPositiveFinite(sample.outputTokens) &&\n\t\tisPositiveFinite(sample.durationMs) &&\n\t\tNumber.isFinite(sample.observedAt) &&\n\t\t(sample.observedAt ?? -1) >= 0\n\t);\n}\n\nfunction normalizeSample(sample: OutputThroughputSample): OutputThroughputSample {\n\treturn {\n\t\tprovider: sample.provider,\n\t\tmodel: sample.model,\n\t\t...(sample.responseModel === undefined ? {} : { responseModel: sample.responseModel }),\n\t\toutputTokens: sample.outputTokens,\n\t\tdurationMs: sample.durationMs,\n\t\tobservedAt: sample.observedAt,\n\t};\n}\n\nfunction isBoundedString(value: unknown): value is string {\n\treturn typeof value === \"string\" && value.length > 0 && value.length <= MAX_STRING_LENGTH;\n}\n\nfunction isPositiveFinite(value: unknown): value is number {\n\treturn typeof value === \"number\" && Number.isFinite(value) && value > 0;\n}\n\nfunction mergeSamples(\n\tsamples: readonly OutputThroughputSample[],\n\tadded: OutputThroughputSample,\n): OutputThroughputSample[] {\n\tconst unique = new Map<string, OutputThroughputSample>();\n\tfor (const sample of [...samples, added]) unique.set(sampleIdentity(sample), normalizeSample(sample));\n\tconst byModel = new Map<string, OutputThroughputSample[]>();\n\tfor (const sample of unique.values()) {\n\t\tconst key = `${sample.provider.length}:${sample.provider}${sample.model}`;\n\t\tconst group = byModel.get(key) ?? [];\n\t\tgroup.push(sample);\n\t\tbyModel.set(key, group);\n\t}\n\treturn [...byModel.values()]\n\t\t.flatMap((group) => group.sort((left, right) => left.observedAt - right.observedAt).slice(-MAX_SAMPLES_PER_MODEL))\n\t\t.sort((left, right) => left.observedAt - right.observedAt)\n\t\t.slice(-MAX_HISTORY_SAMPLES);\n}\n\nfunction sampleIdentity(sample: OutputThroughputSample): string {\n\treturn JSON.stringify([\n\t\tsample.provider,\n\t\tsample.model,\n\t\tsample.responseModel ?? null,\n\t\tsample.outputTokens,\n\t\tsample.durationMs,\n\t\tsample.observedAt,\n\t]);\n}\n\nfunction historyFromSamples(samples: readonly OutputThroughputSample[]): OutputThroughputHistory {\n\tconst history = new OutputThroughputHistory();\n\tfor (const sample of samples) history.add(sample);\n\treturn history;\n}\n\nfunction isMissingFile(cause: unknown): boolean {\n\treturn cause instanceof Error && \"code\" in cause && cause.code === \"ENOENT\";\n}\n"]}
@@ -0,0 +1,43 @@
1
+ import type { AssistantMessage, AssistantMessageEvent } from "@leanandmean/ai";
2
+ export interface OutputThroughputIdentity {
3
+ provider: string;
4
+ model: string;
5
+ }
6
+ export interface OutputThroughputSample extends OutputThroughputIdentity {
7
+ responseModel?: string;
8
+ outputTokens: number;
9
+ durationMs: number;
10
+ observedAt: number;
11
+ }
12
+ export interface OutputThroughputTrackerOptions {
13
+ monotonicNow?: () => number;
14
+ wallNow?: () => number;
15
+ }
16
+ export declare class OutputThroughputTracker {
17
+ private readonly monotonicNow;
18
+ private readonly wallNow;
19
+ private active?;
20
+ private nextGeneration;
21
+ constructor(options?: OutputThroughputTrackerOptions);
22
+ get generation(): number;
23
+ get outputActive(): boolean;
24
+ get activeIdentity(): OutputThroughputIdentity | undefined;
25
+ start(identity: OutputThroughputIdentity): number;
26
+ reset(): void;
27
+ observe(generation: number, event: AssistantMessageEvent): void;
28
+ liveRate(): number | undefined;
29
+ finalize(generation: number, message: AssistantMessage): OutputThroughputSample | undefined;
30
+ }
31
+ export declare class OutputThroughputHistory {
32
+ private readonly samplesByModel;
33
+ add(sample: OutputThroughputSample): void;
34
+ samples(provider: string, model: string): readonly OutputThroughputSample[];
35
+ allSamples(): readonly OutputThroughputSample[];
36
+ latestSample(provider: string, model: string): OutputThroughputSample | undefined;
37
+ latestRate(provider: string, model: string): number | undefined;
38
+ median(provider: string, model: string): number | undefined;
39
+ }
40
+ export declare function formatLiveOutputRate(rate: number): string | undefined;
41
+ export declare function formatCompletedOutputRate(rate: number): string | undefined;
42
+ export declare function formatHistoricalOutputRate(rate: number): string | undefined;
43
+ //# sourceMappingURL=output-throughput.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"output-throughput.d.ts","sourceRoot":"","sources":["../../src/core/output-throughput.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,gBAAgB,EAAE,qBAAqB,EAAE,MAAM,iBAAiB,CAAC;AAS/E,MAAM,WAAW,wBAAwB;IACxC,QAAQ,EAAE,MAAM,CAAC;IACjB,KAAK,EAAE,MAAM,CAAC;CACd;AAED,MAAM,WAAW,sBAAuB,SAAQ,wBAAwB;IACvE,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,YAAY,EAAE,MAAM,CAAC;IACrB,UAAU,EAAE,MAAM,CAAC;IACnB,UAAU,EAAE,MAAM,CAAC;CACnB;AAED,MAAM,WAAW,8BAA8B;IAC9C,YAAY,CAAC,EAAE,MAAM,MAAM,CAAC;IAC5B,OAAO,CAAC,EAAE,MAAM,MAAM,CAAC;CACvB;AAYD,qBAAa,uBAAuB;IACnC,OAAO,CAAC,QAAQ,CAAC,YAAY,CAAe;IAC5C,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAe;IACvC,OAAO,CAAC,MAAM,CAAC,CAAoB;IACnC,OAAO,CAAC,cAAc,CAAK;IAE3B,YAAY,OAAO,GAAE,8BAAmC,EAGvD;IAED,IAAI,UAAU,IAAI,MAAM,CAEvB;IAED,IAAI,YAAY,IAAI,OAAO,CAE1B;IAED,IAAI,cAAc,IAAI,wBAAwB,GAAG,SAAS,CAEzD;IAED,KAAK,CAAC,QAAQ,EAAE,wBAAwB,GAAG,MAAM,CAIhD;IAED,KAAK,IAAI,IAAI,CAGZ;IAED,OAAO,CAAC,UAAU,EAAE,MAAM,EAAE,KAAK,EAAE,qBAAqB,GAAG,IAAI,CAK9D;IAED,QAAQ,IAAI,MAAM,GAAG,SAAS,CAa7B;IAED,QAAQ,CAAC,UAAU,EAAE,MAAM,EAAE,OAAO,EAAE,gBAAgB,GAAG,sBAAsB,GAAG,SAAS,CAgC1F;CACD;AAED,qBAAa,uBAAuB;IACnC,OAAO,CAAC,QAAQ,CAAC,cAAc,CAA+C;IAE9E,GAAG,CAAC,MAAM,EAAE,sBAAsB,GAAG,IAAI,CAOxC;IAED,OAAO,CAAC,QAAQ,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG,SAAS,sBAAsB,EAAE,CAE1E;IAED,UAAU,IAAI,SAAS,sBAAsB,EAAE,CAE9C;IAED,YAAY,CAAC,QAAQ,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG,sBAAsB,GAAG,SAAS,CAEhF;IAED,UAAU,CAAC,QAAQ,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG,MAAM,GAAG,SAAS,CAK9D;IAED,MAAM,CAAC,QAAQ,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG,MAAM,GAAG,SAAS,CAQ1D;CACD;AAED,wBAAgB,oBAAoB,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,GAAG,SAAS,CAErE;AAED,wBAAgB,yBAAyB,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,GAAG,SAAS,CAG1E;AAED,wBAAgB,0BAA0B,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,GAAG,SAAS,CAE3E","sourcesContent":["import type { AssistantMessage, AssistantMessageEvent } from \"@leanandmean/ai\";\n\n// SCRAMJET-DIVERGENCE: Measure and aggregate qualified provider output throughput by requested model (#476).\n\nconst LIVE_WINDOW_MS = 2000;\nconst BYTES_PER_TOKEN = 4;\nconst MAX_SAMPLES_PER_MODEL = 20;\nconst encoder = new TextEncoder();\n\nexport interface OutputThroughputIdentity {\n\tprovider: string;\n\tmodel: string;\n}\n\nexport interface OutputThroughputSample extends OutputThroughputIdentity {\n\tresponseModel?: string;\n\toutputTokens: number;\n\tdurationMs: number;\n\tobservedAt: number;\n}\n\nexport interface OutputThroughputTrackerOptions {\n\tmonotonicNow?: () => number;\n\twallNow?: () => number;\n}\n\ninterface Observation {\n\ttime: number;\n\ttokens: number;\n}\n\ninterface ActiveMeasurement extends OutputThroughputIdentity {\n\tgeneration: number;\n\tobservations: Observation[];\n}\n\nexport class OutputThroughputTracker {\n\tprivate readonly monotonicNow: () => number;\n\tprivate readonly wallNow: () => number;\n\tprivate active?: ActiveMeasurement;\n\tprivate nextGeneration = 0;\n\n\tconstructor(options: OutputThroughputTrackerOptions = {}) {\n\t\tthis.monotonicNow = options.monotonicNow ?? (() => performance.now());\n\t\tthis.wallNow = options.wallNow ?? (() => Date.now());\n\t}\n\n\tget generation(): number {\n\t\treturn this.nextGeneration;\n\t}\n\n\tget outputActive(): boolean {\n\t\treturn (this.active?.observations.length ?? 0) > 0;\n\t}\n\n\tget activeIdentity(): OutputThroughputIdentity | undefined {\n\t\treturn this.active ? { provider: this.active.provider, model: this.active.model } : undefined;\n\t}\n\n\tstart(identity: OutputThroughputIdentity): number {\n\t\tconst generation = ++this.nextGeneration;\n\t\tthis.active = { ...identity, generation, observations: [] };\n\t\treturn generation;\n\t}\n\n\treset(): void {\n\t\tthis.nextGeneration++;\n\t\tthis.active = undefined;\n\t}\n\n\tobserve(generation: number, event: AssistantMessageEvent): void {\n\t\tif (generation !== this.active?.generation || !isGeneratedDelta(event) || event.delta.length === 0) return;\n\t\tconst time = this.monotonicNow();\n\t\tif (!Number.isFinite(time)) return;\n\t\tthis.active.observations.push({ time, tokens: encoder.encode(event.delta).byteLength / BYTES_PER_TOKEN });\n\t}\n\n\tliveRate(): number | undefined {\n\t\tif (!this.active) return undefined;\n\t\tconst now = this.monotonicNow();\n\t\tif (!Number.isFinite(now)) return undefined;\n\t\tconst cutoff = now - LIVE_WINDOW_MS;\n\t\tconst retained = this.active.observations.filter((observation) => observation.time >= cutoff);\n\t\tconst first = retained[0];\n\t\tif (!first) return undefined;\n\t\tconst elapsedMs = now - first.time;\n\t\tif (!(elapsedMs > 0)) return undefined;\n\t\tconst tokens = retained.reduce((total, observation) => total + observation.tokens, 0);\n\t\tconst rate = (tokens * 1000) / elapsedMs;\n\t\treturn Number.isFinite(rate) && rate > 0 ? rate : undefined;\n\t}\n\n\tfinalize(generation: number, message: AssistantMessage): OutputThroughputSample | undefined {\n\t\tif (generation !== this.active?.generation) return undefined;\n\t\tconst active = this.active;\n\t\tthis.reset();\n\t\tconst first = active.observations[0];\n\t\tconst last = active.observations.at(-1);\n\t\tconst outputTokens = message.usage.output;\n\t\tif (\n\t\t\tmessage.origin !== \"provider\" ||\n\t\t\tmessage.provider !== active.provider ||\n\t\t\tmessage.model !== active.model ||\n\t\t\t!isSuccessfulStop(message.stopReason) ||\n\t\t\t!Number.isFinite(outputTokens) ||\n\t\t\toutputTokens <= 0 ||\n\t\t\tactive.observations.length < 2 ||\n\t\t\t!first ||\n\t\t\t!last\n\t\t) {\n\t\t\treturn undefined;\n\t\t}\n\t\tconst durationMs = last.time - first.time;\n\t\tif (!Number.isFinite(durationMs) || durationMs <= 0) return undefined;\n\t\tconst observedAt = this.wallNow();\n\t\tif (!Number.isFinite(observedAt)) return undefined;\n\t\treturn {\n\t\t\tprovider: active.provider,\n\t\t\tmodel: active.model,\n\t\t\t...(message.responseModel ? { responseModel: message.responseModel } : {}),\n\t\t\toutputTokens,\n\t\t\tdurationMs,\n\t\t\tobservedAt,\n\t\t};\n\t}\n}\n\nexport class OutputThroughputHistory {\n\tprivate readonly samplesByModel = new Map<string, OutputThroughputSample[]>();\n\n\tadd(sample: OutputThroughputSample): void {\n\t\tif (!isValidSample(sample)) return;\n\t\tconst key = modelKey(sample.provider, sample.model);\n\t\tconst samples = [...(this.samplesByModel.get(key) ?? []), { ...sample }]\n\t\t\t.sort((left, right) => left.observedAt - right.observedAt)\n\t\t\t.slice(-MAX_SAMPLES_PER_MODEL);\n\t\tthis.samplesByModel.set(key, samples);\n\t}\n\n\tsamples(provider: string, model: string): readonly OutputThroughputSample[] {\n\t\treturn (this.samplesByModel.get(modelKey(provider, model)) ?? []).map((sample) => ({ ...sample }));\n\t}\n\n\tallSamples(): readonly OutputThroughputSample[] {\n\t\treturn [...this.samplesByModel.values()].flat().map((sample) => ({ ...sample }));\n\t}\n\n\tlatestSample(provider: string, model: string): OutputThroughputSample | undefined {\n\t\treturn this.samples(provider, model).at(-1);\n\t}\n\n\tlatestRate(provider: string, model: string): number | undefined {\n\t\tconst sample = this.latestSample(provider, model);\n\t\tif (!sample) return undefined;\n\t\tconst rate = (sample.outputTokens * 1000) / sample.durationMs;\n\t\treturn Number.isFinite(rate) && rate > 0 ? rate : undefined;\n\t}\n\n\tmedian(provider: string, model: string): number | undefined {\n\t\tconst rates = this.samples(provider, model)\n\t\t\t.map((sample) => (sample.outputTokens * 1000) / sample.durationMs)\n\t\t\t.filter((rate) => Number.isFinite(rate) && rate > 0)\n\t\t\t.sort((left, right) => left - right);\n\t\tif (rates.length === 0) return undefined;\n\t\tconst middle = Math.floor(rates.length / 2);\n\t\treturn rates.length % 2 === 0 ? ((rates[middle - 1] ?? 0) + (rates[middle] ?? 0)) / 2 : rates[middle];\n\t}\n}\n\nexport function formatLiveOutputRate(rate: number): string | undefined {\n\treturn formatCompletedOutputRate(rate);\n}\n\nexport function formatCompletedOutputRate(rate: number): string | undefined {\n\tif (!Number.isFinite(rate) || rate <= 0) return undefined;\n\treturn `${Math.round(rate)}tok/s`;\n}\n\nexport function formatHistoricalOutputRate(rate: number): string | undefined {\n\treturn formatCompletedOutputRate(rate);\n}\n\nfunction isGeneratedDelta(\n\tevent: AssistantMessageEvent,\n): event is Extract<AssistantMessageEvent, { type: \"text_delta\" | \"thinking_delta\" | \"toolcall_delta\" }> {\n\treturn event.type === \"text_delta\" || event.type === \"thinking_delta\" || event.type === \"toolcall_delta\";\n}\n\nfunction isSuccessfulStop(stopReason: AssistantMessage[\"stopReason\"]): boolean {\n\treturn stopReason === \"stop\" || stopReason === \"length\" || stopReason === \"toolUse\";\n}\n\nfunction isValidSample(sample: OutputThroughputSample): boolean {\n\treturn (\n\t\tsample.provider.length > 0 &&\n\t\tsample.model.length > 0 &&\n\t\tNumber.isFinite(sample.outputTokens) &&\n\t\tsample.outputTokens > 0 &&\n\t\tNumber.isFinite(sample.durationMs) &&\n\t\tsample.durationMs > 0 &&\n\t\tNumber.isFinite(sample.observedAt)\n\t);\n}\n\nfunction modelKey(provider: string, model: string): string {\n\treturn `${provider.length}:${provider}${model}`;\n}\n"]}
@@ -0,0 +1,161 @@
1
+ // SCRAMJET-DIVERGENCE: Measure and aggregate qualified provider output throughput by requested model (#476).
2
+ const LIVE_WINDOW_MS = 2000;
3
+ const BYTES_PER_TOKEN = 4;
4
+ const MAX_SAMPLES_PER_MODEL = 20;
5
+ const encoder = new TextEncoder();
6
+ export class OutputThroughputTracker {
7
+ monotonicNow;
8
+ wallNow;
9
+ active;
10
+ nextGeneration = 0;
11
+ constructor(options = {}) {
12
+ this.monotonicNow = options.monotonicNow ?? (() => performance.now());
13
+ this.wallNow = options.wallNow ?? (() => Date.now());
14
+ }
15
+ get generation() {
16
+ return this.nextGeneration;
17
+ }
18
+ get outputActive() {
19
+ return (this.active?.observations.length ?? 0) > 0;
20
+ }
21
+ get activeIdentity() {
22
+ return this.active ? { provider: this.active.provider, model: this.active.model } : undefined;
23
+ }
24
+ start(identity) {
25
+ const generation = ++this.nextGeneration;
26
+ this.active = { ...identity, generation, observations: [] };
27
+ return generation;
28
+ }
29
+ reset() {
30
+ this.nextGeneration++;
31
+ this.active = undefined;
32
+ }
33
+ observe(generation, event) {
34
+ if (generation !== this.active?.generation || !isGeneratedDelta(event) || event.delta.length === 0)
35
+ return;
36
+ const time = this.monotonicNow();
37
+ if (!Number.isFinite(time))
38
+ return;
39
+ this.active.observations.push({ time, tokens: encoder.encode(event.delta).byteLength / BYTES_PER_TOKEN });
40
+ }
41
+ liveRate() {
42
+ if (!this.active)
43
+ return undefined;
44
+ const now = this.monotonicNow();
45
+ if (!Number.isFinite(now))
46
+ return undefined;
47
+ const cutoff = now - LIVE_WINDOW_MS;
48
+ const retained = this.active.observations.filter((observation) => observation.time >= cutoff);
49
+ const first = retained[0];
50
+ if (!first)
51
+ return undefined;
52
+ const elapsedMs = now - first.time;
53
+ if (!(elapsedMs > 0))
54
+ return undefined;
55
+ const tokens = retained.reduce((total, observation) => total + observation.tokens, 0);
56
+ const rate = (tokens * 1000) / elapsedMs;
57
+ return Number.isFinite(rate) && rate > 0 ? rate : undefined;
58
+ }
59
+ finalize(generation, message) {
60
+ if (generation !== this.active?.generation)
61
+ return undefined;
62
+ const active = this.active;
63
+ this.reset();
64
+ const first = active.observations[0];
65
+ const last = active.observations.at(-1);
66
+ const outputTokens = message.usage.output;
67
+ if (message.origin !== "provider" ||
68
+ message.provider !== active.provider ||
69
+ message.model !== active.model ||
70
+ !isSuccessfulStop(message.stopReason) ||
71
+ !Number.isFinite(outputTokens) ||
72
+ outputTokens <= 0 ||
73
+ active.observations.length < 2 ||
74
+ !first ||
75
+ !last) {
76
+ return undefined;
77
+ }
78
+ const durationMs = last.time - first.time;
79
+ if (!Number.isFinite(durationMs) || durationMs <= 0)
80
+ return undefined;
81
+ const observedAt = this.wallNow();
82
+ if (!Number.isFinite(observedAt))
83
+ return undefined;
84
+ return {
85
+ provider: active.provider,
86
+ model: active.model,
87
+ ...(message.responseModel ? { responseModel: message.responseModel } : {}),
88
+ outputTokens,
89
+ durationMs,
90
+ observedAt,
91
+ };
92
+ }
93
+ }
94
+ export class OutputThroughputHistory {
95
+ samplesByModel = new Map();
96
+ add(sample) {
97
+ if (!isValidSample(sample))
98
+ return;
99
+ const key = modelKey(sample.provider, sample.model);
100
+ const samples = [...(this.samplesByModel.get(key) ?? []), { ...sample }]
101
+ .sort((left, right) => left.observedAt - right.observedAt)
102
+ .slice(-MAX_SAMPLES_PER_MODEL);
103
+ this.samplesByModel.set(key, samples);
104
+ }
105
+ samples(provider, model) {
106
+ return (this.samplesByModel.get(modelKey(provider, model)) ?? []).map((sample) => ({ ...sample }));
107
+ }
108
+ allSamples() {
109
+ return [...this.samplesByModel.values()].flat().map((sample) => ({ ...sample }));
110
+ }
111
+ latestSample(provider, model) {
112
+ return this.samples(provider, model).at(-1);
113
+ }
114
+ latestRate(provider, model) {
115
+ const sample = this.latestSample(provider, model);
116
+ if (!sample)
117
+ return undefined;
118
+ const rate = (sample.outputTokens * 1000) / sample.durationMs;
119
+ return Number.isFinite(rate) && rate > 0 ? rate : undefined;
120
+ }
121
+ median(provider, model) {
122
+ const rates = this.samples(provider, model)
123
+ .map((sample) => (sample.outputTokens * 1000) / sample.durationMs)
124
+ .filter((rate) => Number.isFinite(rate) && rate > 0)
125
+ .sort((left, right) => left - right);
126
+ if (rates.length === 0)
127
+ return undefined;
128
+ const middle = Math.floor(rates.length / 2);
129
+ return rates.length % 2 === 0 ? ((rates[middle - 1] ?? 0) + (rates[middle] ?? 0)) / 2 : rates[middle];
130
+ }
131
+ }
132
+ export function formatLiveOutputRate(rate) {
133
+ return formatCompletedOutputRate(rate);
134
+ }
135
+ export function formatCompletedOutputRate(rate) {
136
+ if (!Number.isFinite(rate) || rate <= 0)
137
+ return undefined;
138
+ return `${Math.round(rate)}tok/s`;
139
+ }
140
+ export function formatHistoricalOutputRate(rate) {
141
+ return formatCompletedOutputRate(rate);
142
+ }
143
+ function isGeneratedDelta(event) {
144
+ return event.type === "text_delta" || event.type === "thinking_delta" || event.type === "toolcall_delta";
145
+ }
146
+ function isSuccessfulStop(stopReason) {
147
+ return stopReason === "stop" || stopReason === "length" || stopReason === "toolUse";
148
+ }
149
+ function isValidSample(sample) {
150
+ return (sample.provider.length > 0 &&
151
+ sample.model.length > 0 &&
152
+ Number.isFinite(sample.outputTokens) &&
153
+ sample.outputTokens > 0 &&
154
+ Number.isFinite(sample.durationMs) &&
155
+ sample.durationMs > 0 &&
156
+ Number.isFinite(sample.observedAt));
157
+ }
158
+ function modelKey(provider, model) {
159
+ return `${provider.length}:${provider}${model}`;
160
+ }
161
+ //# sourceMappingURL=output-throughput.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"output-throughput.js","sourceRoot":"","sources":["../../src/core/output-throughput.ts"],"names":[],"mappings":"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","sourcesContent":["import type { AssistantMessage, AssistantMessageEvent } from \"@leanandmean/ai\";\n\n// SCRAMJET-DIVERGENCE: Measure and aggregate qualified provider output throughput by requested model (#476).\n\nconst LIVE_WINDOW_MS = 2000;\nconst BYTES_PER_TOKEN = 4;\nconst MAX_SAMPLES_PER_MODEL = 20;\nconst encoder = new TextEncoder();\n\nexport interface OutputThroughputIdentity {\n\tprovider: string;\n\tmodel: string;\n}\n\nexport interface OutputThroughputSample extends OutputThroughputIdentity {\n\tresponseModel?: string;\n\toutputTokens: number;\n\tdurationMs: number;\n\tobservedAt: number;\n}\n\nexport interface OutputThroughputTrackerOptions {\n\tmonotonicNow?: () => number;\n\twallNow?: () => number;\n}\n\ninterface Observation {\n\ttime: number;\n\ttokens: number;\n}\n\ninterface ActiveMeasurement extends OutputThroughputIdentity {\n\tgeneration: number;\n\tobservations: Observation[];\n}\n\nexport class OutputThroughputTracker {\n\tprivate readonly monotonicNow: () => number;\n\tprivate readonly wallNow: () => number;\n\tprivate active?: ActiveMeasurement;\n\tprivate nextGeneration = 0;\n\n\tconstructor(options: OutputThroughputTrackerOptions = {}) {\n\t\tthis.monotonicNow = options.monotonicNow ?? (() => performance.now());\n\t\tthis.wallNow = options.wallNow ?? (() => Date.now());\n\t}\n\n\tget generation(): number {\n\t\treturn this.nextGeneration;\n\t}\n\n\tget outputActive(): boolean {\n\t\treturn (this.active?.observations.length ?? 0) > 0;\n\t}\n\n\tget activeIdentity(): OutputThroughputIdentity | undefined {\n\t\treturn this.active ? { provider: this.active.provider, model: this.active.model } : undefined;\n\t}\n\n\tstart(identity: OutputThroughputIdentity): number {\n\t\tconst generation = ++this.nextGeneration;\n\t\tthis.active = { ...identity, generation, observations: [] };\n\t\treturn generation;\n\t}\n\n\treset(): void {\n\t\tthis.nextGeneration++;\n\t\tthis.active = undefined;\n\t}\n\n\tobserve(generation: number, event: AssistantMessageEvent): void {\n\t\tif (generation !== this.active?.generation || !isGeneratedDelta(event) || event.delta.length === 0) return;\n\t\tconst time = this.monotonicNow();\n\t\tif (!Number.isFinite(time)) return;\n\t\tthis.active.observations.push({ time, tokens: encoder.encode(event.delta).byteLength / BYTES_PER_TOKEN });\n\t}\n\n\tliveRate(): number | undefined {\n\t\tif (!this.active) return undefined;\n\t\tconst now = this.monotonicNow();\n\t\tif (!Number.isFinite(now)) return undefined;\n\t\tconst cutoff = now - LIVE_WINDOW_MS;\n\t\tconst retained = this.active.observations.filter((observation) => observation.time >= cutoff);\n\t\tconst first = retained[0];\n\t\tif (!first) return undefined;\n\t\tconst elapsedMs = now - first.time;\n\t\tif (!(elapsedMs > 0)) return undefined;\n\t\tconst tokens = retained.reduce((total, observation) => total + observation.tokens, 0);\n\t\tconst rate = (tokens * 1000) / elapsedMs;\n\t\treturn Number.isFinite(rate) && rate > 0 ? rate : undefined;\n\t}\n\n\tfinalize(generation: number, message: AssistantMessage): OutputThroughputSample | undefined {\n\t\tif (generation !== this.active?.generation) return undefined;\n\t\tconst active = this.active;\n\t\tthis.reset();\n\t\tconst first = active.observations[0];\n\t\tconst last = active.observations.at(-1);\n\t\tconst outputTokens = message.usage.output;\n\t\tif (\n\t\t\tmessage.origin !== \"provider\" ||\n\t\t\tmessage.provider !== active.provider ||\n\t\t\tmessage.model !== active.model ||\n\t\t\t!isSuccessfulStop(message.stopReason) ||\n\t\t\t!Number.isFinite(outputTokens) ||\n\t\t\toutputTokens <= 0 ||\n\t\t\tactive.observations.length < 2 ||\n\t\t\t!first ||\n\t\t\t!last\n\t\t) {\n\t\t\treturn undefined;\n\t\t}\n\t\tconst durationMs = last.time - first.time;\n\t\tif (!Number.isFinite(durationMs) || durationMs <= 0) return undefined;\n\t\tconst observedAt = this.wallNow();\n\t\tif (!Number.isFinite(observedAt)) return undefined;\n\t\treturn {\n\t\t\tprovider: active.provider,\n\t\t\tmodel: active.model,\n\t\t\t...(message.responseModel ? { responseModel: message.responseModel } : {}),\n\t\t\toutputTokens,\n\t\t\tdurationMs,\n\t\t\tobservedAt,\n\t\t};\n\t}\n}\n\nexport class OutputThroughputHistory {\n\tprivate readonly samplesByModel = new Map<string, OutputThroughputSample[]>();\n\n\tadd(sample: OutputThroughputSample): void {\n\t\tif (!isValidSample(sample)) return;\n\t\tconst key = modelKey(sample.provider, sample.model);\n\t\tconst samples = [...(this.samplesByModel.get(key) ?? []), { ...sample }]\n\t\t\t.sort((left, right) => left.observedAt - right.observedAt)\n\t\t\t.slice(-MAX_SAMPLES_PER_MODEL);\n\t\tthis.samplesByModel.set(key, samples);\n\t}\n\n\tsamples(provider: string, model: string): readonly OutputThroughputSample[] {\n\t\treturn (this.samplesByModel.get(modelKey(provider, model)) ?? []).map((sample) => ({ ...sample }));\n\t}\n\n\tallSamples(): readonly OutputThroughputSample[] {\n\t\treturn [...this.samplesByModel.values()].flat().map((sample) => ({ ...sample }));\n\t}\n\n\tlatestSample(provider: string, model: string): OutputThroughputSample | undefined {\n\t\treturn this.samples(provider, model).at(-1);\n\t}\n\n\tlatestRate(provider: string, model: string): number | undefined {\n\t\tconst sample = this.latestSample(provider, model);\n\t\tif (!sample) return undefined;\n\t\tconst rate = (sample.outputTokens * 1000) / sample.durationMs;\n\t\treturn Number.isFinite(rate) && rate > 0 ? rate : undefined;\n\t}\n\n\tmedian(provider: string, model: string): number | undefined {\n\t\tconst rates = this.samples(provider, model)\n\t\t\t.map((sample) => (sample.outputTokens * 1000) / sample.durationMs)\n\t\t\t.filter((rate) => Number.isFinite(rate) && rate > 0)\n\t\t\t.sort((left, right) => left - right);\n\t\tif (rates.length === 0) return undefined;\n\t\tconst middle = Math.floor(rates.length / 2);\n\t\treturn rates.length % 2 === 0 ? ((rates[middle - 1] ?? 0) + (rates[middle] ?? 0)) / 2 : rates[middle];\n\t}\n}\n\nexport function formatLiveOutputRate(rate: number): string | undefined {\n\treturn formatCompletedOutputRate(rate);\n}\n\nexport function formatCompletedOutputRate(rate: number): string | undefined {\n\tif (!Number.isFinite(rate) || rate <= 0) return undefined;\n\treturn `${Math.round(rate)}tok/s`;\n}\n\nexport function formatHistoricalOutputRate(rate: number): string | undefined {\n\treturn formatCompletedOutputRate(rate);\n}\n\nfunction isGeneratedDelta(\n\tevent: AssistantMessageEvent,\n): event is Extract<AssistantMessageEvent, { type: \"text_delta\" | \"thinking_delta\" | \"toolcall_delta\" }> {\n\treturn event.type === \"text_delta\" || event.type === \"thinking_delta\" || event.type === \"toolcall_delta\";\n}\n\nfunction isSuccessfulStop(stopReason: AssistantMessage[\"stopReason\"]): boolean {\n\treturn stopReason === \"stop\" || stopReason === \"length\" || stopReason === \"toolUse\";\n}\n\nfunction isValidSample(sample: OutputThroughputSample): boolean {\n\treturn (\n\t\tsample.provider.length > 0 &&\n\t\tsample.model.length > 0 &&\n\t\tNumber.isFinite(sample.outputTokens) &&\n\t\tsample.outputTokens > 0 &&\n\t\tNumber.isFinite(sample.durationMs) &&\n\t\tsample.durationMs > 0 &&\n\t\tNumber.isFinite(sample.observedAt)\n\t);\n}\n\nfunction modelKey(provider: string, model: string): string {\n\treturn `${provider.length}:${provider}${model}`;\n}\n"]}
@@ -13,6 +13,8 @@ export interface CreateAgentSessionOptions {
13
13
  cwd?: string;
14
14
  /** Global config directory. Default: ~/.scramjet/agent */
15
15
  agentDir?: string;
16
+ /** Output-throughput history path resolved by runtime services. */
17
+ outputThroughputHistoryPath?: string;
16
18
  /** Auth storage for credentials. Default: AuthStorage.create(agentDir/auth.json) */
17
19
  authStorage?: AuthStorage;
18
20
  /** Model registry. Default: ModelRegistry.create(authStorage, agentDir/models.json) */