@yegor256/jinka 0.0.7 → 0.0.9
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/bin/jinka.js +66 -48
- package/package.json +1 -1
- package/src/agent.js +4 -7
- package/src/archived.js +22 -30
- package/src/claude.js +14 -24
- package/src/codex.js +17 -27
- package/src/colorless.js +13 -0
- package/src/crew.js +56 -61
- package/src/disk.js +20 -28
- package/src/duration.js +26 -26
- package/src/ellipsized.js +10 -0
- package/src/event.js +76 -82
- package/src/freeze.js +36 -41
- package/src/frozen.js +19 -21
- package/src/genie.js +18 -23
- package/src/jinka.js +7 -13
- package/src/jobs.js +13 -16
- package/src/keys.js +19 -23
- package/src/legend.js +9 -13
- package/src/limited.js +12 -15
- package/src/log.js +8 -17
- package/src/logo.js +3 -5
- package/src/meters.js +3 -8
- package/src/model.js +20 -20
- package/src/permanent.js +9 -21
- package/src/protocol.js +4 -11
- package/src/quota.js +22 -32
- package/src/release.js +30 -0
- package/src/rollouts.js +38 -40
- package/src/row.js +3 -9
- package/src/semaphore.js +16 -0
- package/src/status.js +10 -18
- package/src/summarized.js +15 -26
- package/src/summary.js +11 -19
- package/src/throttled.js +19 -24
- package/src/tokens.js +23 -25
- package/src/tty.js +79 -87
- package/src/wand.js +26 -29
- package/src/wheel.js +5 -9
- package/src/wish.js +10 -23
- package/src/wishes.js +25 -31
package/src/permanent.js
CHANGED
|
@@ -1,24 +1,12 @@
|
|
|
1
1
|
// SPDX-FileCopyrightText: Copyright (c) 2026 Yegor Bugayenko
|
|
2
2
|
// SPDX-License-Identifier: MIT
|
|
3
3
|
|
|
4
|
-
export
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
return this.origin.text();
|
|
14
|
-
}
|
|
15
|
-
fate() {
|
|
16
|
-
return 'which stays there after you exit, so you must not delete it';
|
|
17
|
-
}
|
|
18
|
-
async close() {
|
|
19
|
-
setTimeout(() => this.origin.free(), this.pause).unref();
|
|
20
|
-
}
|
|
21
|
-
free() {
|
|
22
|
-
this.origin.free();
|
|
23
|
-
}
|
|
24
|
-
}
|
|
4
|
+
export const permanent = (origin, pause) => Object.freeze({
|
|
5
|
+
name: () => origin.name(),
|
|
6
|
+
text: async () => origin.text(),
|
|
7
|
+
fate: () => 'which stays there after you exit, so you must not delete it',
|
|
8
|
+
close: async () => {
|
|
9
|
+
setTimeout(() => origin.free(), pause).unref();
|
|
10
|
+
},
|
|
11
|
+
free: () => origin.free()
|
|
12
|
+
});
|
package/src/protocol.js
CHANGED
|
@@ -1,14 +1,7 @@
|
|
|
1
1
|
// SPDX-FileCopyrightText: Copyright (c) 2026 Yegor Bugayenko
|
|
2
2
|
// SPDX-License-Identifier: MIT
|
|
3
3
|
|
|
4
|
-
export
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
info(text) {
|
|
9
|
-
this.lines.push(['info', text]);
|
|
10
|
-
}
|
|
11
|
-
warn(text) {
|
|
12
|
-
this.lines.push(['warn', text]);
|
|
13
|
-
}
|
|
14
|
-
}
|
|
4
|
+
export const protocol = (lines) => Object.freeze({
|
|
5
|
+
info: (text) => lines.push(['info', text]),
|
|
6
|
+
warn: (text) => lines.push(['warn', text])
|
|
7
|
+
});
|
package/src/quota.js
CHANGED
|
@@ -1,37 +1,27 @@
|
|
|
1
1
|
// SPDX-FileCopyrightText: Copyright (c) 2026 Yegor Bugayenko
|
|
2
2
|
// SPDX-License-Identifier: MIT
|
|
3
3
|
|
|
4
|
-
export
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
4
|
+
export const quota = () => {
|
|
5
|
+
const windows = [];
|
|
6
|
+
const live = (now) => (windows.at(-1) ?? [])
|
|
7
|
+
.filter(([, w]) => typeof w.resetsAt !== 'number' || w.resetsAt * 1000 > now)
|
|
8
|
+
.map(([n, w]) => [n.replaceAll('_', '-'), (1 - w.utilization) * 100]);
|
|
9
|
+
const tightest = (now) => live(now).reduce((a, b) => (b[1] < a[1] ? b : a), ['', 100]);
|
|
10
|
+
return Object.freeze({
|
|
11
|
+
add: (line) => {
|
|
12
|
+
try {
|
|
13
|
+
const json = JSON.parse(line);
|
|
14
|
+
if (json.type === 'rate_limit_event' && typeof json.rate_limit_info?.unifiedWindows === 'object') {
|
|
15
|
+
windows.push(Object.entries(json.rate_limit_info.unifiedWindows).filter(([, w]) => typeof w?.utilization === 'number'));
|
|
16
|
+
}
|
|
17
|
+
} catch {
|
|
18
|
+
return;
|
|
13
19
|
}
|
|
14
|
-
}
|
|
15
|
-
|
|
16
|
-
}
|
|
17
|
-
}
|
|
18
|
-
text(now) {
|
|
19
|
-
return this.#live(now)
|
|
20
|
+
},
|
|
21
|
+
text: (now) => live(now)
|
|
20
22
|
.map(([n, share]) => `${Math.round(share)}%-${{ 'five-hour': '5H', 'seven-day': 'W', 'thirty-day': 'M', 'seven-day-overage-included': 'W+' }[n] ?? n}`)
|
|
21
|
-
.join(',') || '-'
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
name(now) {
|
|
27
|
-
return this.#tightest(now)[0];
|
|
28
|
-
}
|
|
29
|
-
#tightest(now) {
|
|
30
|
-
return this.#live(now).reduce((a, b) => (b[1] < a[1] ? b : a), ['', 100]);
|
|
31
|
-
}
|
|
32
|
-
#live(now) {
|
|
33
|
-
return (this.windows.at(-1) ?? [])
|
|
34
|
-
.filter(([, w]) => typeof w.resetsAt !== 'number' || w.resetsAt * 1000 > now)
|
|
35
|
-
.map(([n, w]) => [n.replaceAll('_', '-'), (1 - w.utilization) * 100]);
|
|
36
|
-
}
|
|
37
|
-
}
|
|
23
|
+
.join(',') || '-',
|
|
24
|
+
left: (now) => tightest(now)[1],
|
|
25
|
+
name: (now) => tightest(now)[0]
|
|
26
|
+
});
|
|
27
|
+
};
|
package/src/release.js
ADDED
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
// SPDX-FileCopyrightText: Copyright (c) 2026 Yegor Bugayenko
|
|
2
|
+
// SPDX-License-Identifier: MIT
|
|
3
|
+
|
|
4
|
+
import { styleText } from 'node:util';
|
|
5
|
+
|
|
6
|
+
export const release = (installed, url) => {
|
|
7
|
+
const latest = [];
|
|
8
|
+
const higher = (left, right) => {
|
|
9
|
+
const [a, b] = [left, right].map((v) => v.split('-')[0].split('.').map(Number));
|
|
10
|
+
const index = a.findIndex((n, i) => n !== (b[i] ?? 0));
|
|
11
|
+
return index >= 0 && a[index] > (b[index] ?? 0);
|
|
12
|
+
};
|
|
13
|
+
return Object.freeze({
|
|
14
|
+
check: async () => {
|
|
15
|
+
try {
|
|
16
|
+
const res = await fetch(url, { signal: AbortSignal.timeout(10000) });
|
|
17
|
+
if (res.ok) {
|
|
18
|
+
latest.push(String((await res.json()).version));
|
|
19
|
+
}
|
|
20
|
+
} catch {
|
|
21
|
+
return;
|
|
22
|
+
}
|
|
23
|
+
},
|
|
24
|
+
text: () => (
|
|
25
|
+
latest.some((v) => higher(v, installed))
|
|
26
|
+
? `${installed} ${styleText('red', '(old)', { validateStream: false })}`
|
|
27
|
+
: installed
|
|
28
|
+
)
|
|
29
|
+
});
|
|
30
|
+
};
|
package/src/rollouts.js
CHANGED
|
@@ -4,50 +4,48 @@
|
|
|
4
4
|
import { existsSync, readdirSync, readFileSync } from 'node:fs';
|
|
5
5
|
import { join } from 'node:path';
|
|
6
6
|
|
|
7
|
-
export
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
}
|
|
14
|
-
add(line) {
|
|
15
|
-
this.origin.add(line);
|
|
16
|
-
try {
|
|
17
|
-
const json = JSON.parse(line);
|
|
18
|
-
if (json.type === 'thread.started' && typeof json.thread_id === 'string') {
|
|
19
|
-
this.thread = json.thread_id;
|
|
20
|
-
}
|
|
21
|
-
if ((json.type === 'turn.completed' || json.type === 'item.completed') && this.thread !== '') {
|
|
22
|
-
this.#limits().forEach((limits) => this.origin.add(JSON.stringify({
|
|
23
|
-
type: 'rate_limit_event',
|
|
24
|
-
rate_limit_info: {
|
|
25
|
-
unifiedWindows: Object.fromEntries(
|
|
26
|
-
[limits.primary, limits.secondary]
|
|
27
|
-
.filter((w) => typeof w?.used_percent === 'number' && typeof w?.window_minutes === 'number')
|
|
28
|
-
.map((w) => [{ 300: 'five_hour', 10080: 'seven_day', 43200: 'thirty_day' }[w.window_minutes] ?? `${w.window_minutes}_minute`, { utilization: w.used_percent / 100, resetsAt: w.resets_at }])
|
|
29
|
-
)
|
|
30
|
-
}
|
|
31
|
-
})));
|
|
32
|
-
}
|
|
33
|
-
} catch {
|
|
34
|
-
return;
|
|
35
|
-
}
|
|
36
|
-
}
|
|
37
|
-
#limits() {
|
|
38
|
-
if (!this.files.has(this.thread) && existsSync(this.dir)) {
|
|
39
|
-
readdirSync(this.dir, { recursive: true })
|
|
7
|
+
export const rollouts = (origin, dir) => {
|
|
8
|
+
const files = new Map();
|
|
9
|
+
let thread = '';
|
|
10
|
+
const limits = () => {
|
|
11
|
+
if (!files.has(thread) && existsSync(dir)) {
|
|
12
|
+
readdirSync(dir, { recursive: true })
|
|
40
13
|
.map(String)
|
|
41
|
-
.filter((path) => path.endsWith(`-${
|
|
42
|
-
.forEach((path) =>
|
|
14
|
+
.filter((path) => path.endsWith(`-${thread}.jsonl`))
|
|
15
|
+
.forEach((path) => files.set(thread, path));
|
|
43
16
|
}
|
|
44
|
-
return !
|
|
17
|
+
return !files.has(thread)
|
|
45
18
|
? []
|
|
46
|
-
: readFileSync(join(
|
|
19
|
+
: readFileSync(join(dir, files.get(thread)), 'utf8')
|
|
47
20
|
.split('\n')
|
|
48
21
|
.filter((text) => text.includes('"rate_limits"'))
|
|
49
22
|
.map((text) => JSON.parse(text).payload?.rate_limits)
|
|
50
|
-
.filter((
|
|
23
|
+
.filter((l) => typeof l === 'object' && l !== null)
|
|
51
24
|
.slice(-1);
|
|
52
|
-
}
|
|
53
|
-
|
|
25
|
+
};
|
|
26
|
+
return Object.freeze({
|
|
27
|
+
add: (line) => {
|
|
28
|
+
origin.add(line);
|
|
29
|
+
try {
|
|
30
|
+
const json = JSON.parse(line);
|
|
31
|
+
if (json.type === 'thread.started' && typeof json.thread_id === 'string') {
|
|
32
|
+
thread = json.thread_id;
|
|
33
|
+
}
|
|
34
|
+
if ((json.type === 'turn.completed' || json.type === 'item.completed') && thread !== '') {
|
|
35
|
+
limits().forEach((l) => origin.add(JSON.stringify({
|
|
36
|
+
type: 'rate_limit_event',
|
|
37
|
+
rate_limit_info: {
|
|
38
|
+
unifiedWindows: Object.fromEntries(
|
|
39
|
+
[l.primary, l.secondary]
|
|
40
|
+
.filter((w) => typeof w?.used_percent === 'number' && typeof w?.window_minutes === 'number')
|
|
41
|
+
.map((w) => [{ 300: 'five_hour', 10080: 'seven_day', 43200: 'thirty_day' }[w.window_minutes] ?? `${w.window_minutes}_minute`, { utilization: w.used_percent / 100, resetsAt: w.resets_at }])
|
|
42
|
+
)
|
|
43
|
+
}
|
|
44
|
+
})));
|
|
45
|
+
}
|
|
46
|
+
} catch {
|
|
47
|
+
return;
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
});
|
|
51
|
+
};
|
package/src/row.js
CHANGED
|
@@ -1,12 +1,6 @@
|
|
|
1
1
|
// SPDX-FileCopyrightText: Copyright (c) 2026 Yegor Bugayenko
|
|
2
2
|
// SPDX-License-Identifier: MIT
|
|
3
3
|
|
|
4
|
-
export
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
this.index = index;
|
|
8
|
-
}
|
|
9
|
-
show(text) {
|
|
10
|
-
this.lines.push([this.index, text]);
|
|
11
|
-
}
|
|
12
|
-
}
|
|
4
|
+
export const row = (lines, index) => Object.freeze({
|
|
5
|
+
show: (text) => lines.push([index, text])
|
|
6
|
+
});
|
package/src/semaphore.js
ADDED
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
// SPDX-FileCopyrightText: Copyright (c) 2026 Yegor Bugayenko
|
|
2
|
+
// SPDX-License-Identifier: MIT
|
|
3
|
+
|
|
4
|
+
import { rmSync } from 'node:fs';
|
|
5
|
+
import { mkdir, writeFile } from 'node:fs/promises';
|
|
6
|
+
import { dirname } from 'node:path';
|
|
7
|
+
|
|
8
|
+
export const semaphore = (file) => Object.freeze({
|
|
9
|
+
acquire: async () => {
|
|
10
|
+
await mkdir(dirname(file), { recursive: true });
|
|
11
|
+
await writeFile(file, String(process.pid), { flag: 'wx' }).catch((e) => {
|
|
12
|
+
throw e.code === 'EEXIST' ? new Error(`Another jinka is running, because the file ${file} already exists`) : e;
|
|
13
|
+
});
|
|
14
|
+
},
|
|
15
|
+
release: () => rmSync(file, { force: true })
|
|
16
|
+
});
|
package/src/status.js
CHANGED
|
@@ -2,26 +2,18 @@
|
|
|
2
2
|
// SPDX-License-Identifier: MIT
|
|
3
3
|
|
|
4
4
|
import { styleText } from 'node:util';
|
|
5
|
-
import {
|
|
5
|
+
import { duration } from './duration.js';
|
|
6
6
|
|
|
7
|
-
export
|
|
8
|
-
|
|
9
|
-
this.start = start;
|
|
10
|
-
this.jobs = jobs;
|
|
11
|
-
this.agent = agent;
|
|
12
|
-
this.model = model;
|
|
13
|
-
this.tokens = tokens;
|
|
14
|
-
this.quota = quota;
|
|
15
|
-
}
|
|
16
|
-
text() {
|
|
7
|
+
export const status = (start, jobs, agent, model, tokens, quota) => Object.freeze({
|
|
8
|
+
text: () => {
|
|
17
9
|
const now = Date.now();
|
|
18
10
|
return [
|
|
19
|
-
`up:${styleText('bgGray',
|
|
20
|
-
`done:${styleText('bgGray',
|
|
21
|
-
`agent:${styleText('bgGray',
|
|
22
|
-
`model:${styleText('bgGray',
|
|
23
|
-
`tokens:${styleText('bgGray',
|
|
24
|
-
`quota:${styleText('bgGray',
|
|
11
|
+
`up:${styleText('bgGray', duration(now - start).hours(), { validateStream: false })}`,
|
|
12
|
+
`done:${styleText('bgGray', jobs.text(), { validateStream: false })}`,
|
|
13
|
+
`agent:${styleText('bgGray', agent, { validateStream: false })}`,
|
|
14
|
+
`model:${styleText('bgGray', model === '' ? 'default' : model, { validateStream: false })}`,
|
|
15
|
+
`tokens:${styleText('bgGray', tokens.text(), { validateStream: false })}`,
|
|
16
|
+
`quota:${styleText('bgGray', quota.text(now), { validateStream: false })}`
|
|
25
17
|
].join(' ');
|
|
26
18
|
}
|
|
27
|
-
}
|
|
19
|
+
});
|
package/src/summarized.js
CHANGED
|
@@ -1,30 +1,19 @@
|
|
|
1
1
|
// SPDX-FileCopyrightText: Copyright (c) 2026 Yegor Bugayenko
|
|
2
2
|
// SPDX-License-Identifier: MIT
|
|
3
3
|
|
|
4
|
-
export
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
up() {
|
|
18
|
-
this.screen.up();
|
|
19
|
-
}
|
|
20
|
-
down() {
|
|
21
|
-
this.screen.down();
|
|
22
|
-
}
|
|
23
|
-
close() {
|
|
24
|
-
this.screen.close();
|
|
25
|
-
if (this.done.length === 0) {
|
|
26
|
-
this.done.push(true);
|
|
27
|
-
this.stream.write(this.summary.text());
|
|
4
|
+
export const summarized = (screen, summary, stream) => {
|
|
5
|
+
const done = [];
|
|
6
|
+
return Object.freeze({
|
|
7
|
+
row: (index) => screen.row(index),
|
|
8
|
+
notice: () => screen.notice(),
|
|
9
|
+
up: () => screen.up(),
|
|
10
|
+
down: () => screen.down(),
|
|
11
|
+
close: () => {
|
|
12
|
+
screen.close();
|
|
13
|
+
if (done.length === 0) {
|
|
14
|
+
done.push(true);
|
|
15
|
+
stream.write(summary.text());
|
|
16
|
+
}
|
|
28
17
|
}
|
|
29
|
-
}
|
|
30
|
-
}
|
|
18
|
+
});
|
|
19
|
+
};
|
package/src/summary.js
CHANGED
|
@@ -4,23 +4,15 @@
|
|
|
4
4
|
import { existsSync, readdirSync } from 'node:fs';
|
|
5
5
|
import { homedir } from 'node:os';
|
|
6
6
|
import { sep } from 'node:path';
|
|
7
|
-
import {
|
|
7
|
+
import { duration } from './duration.js';
|
|
8
8
|
|
|
9
|
-
export
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
`jinka ${this.version} finished in ${new Duration(Date.now() - this.start).hours()}`,
|
|
20
|
-
`agent: ${this.setup.agent}, model: ${this.setup.model === '' ? 'default' : this.setup.model}, effort: ${this.setup.effort === '' ? 'default' : this.setup.effort}`,
|
|
21
|
-
`sessions finished: ${this.jobs.text()}`,
|
|
22
|
-
`wishes remained in ${this.setup.wishes.startsWith(`${homedir()}${sep}`) ? `~${this.setup.wishes.slice(homedir().length)}` : this.setup.wishes}: ${existsSync(this.setup.wishes) ? readdirSync(this.setup.wishes, { recursive: true, withFileTypes: true }).filter((f) => f.isFile() && f.name.endsWith('.md')).length : 0}`,
|
|
23
|
-
''
|
|
24
|
-
].join('\n');
|
|
25
|
-
}
|
|
26
|
-
}
|
|
9
|
+
export const summary = (start, version, jobs, setup) => Object.freeze({
|
|
10
|
+
text: () => [
|
|
11
|
+
'---',
|
|
12
|
+
`jinka ${version} finished in ${duration(Date.now() - start).hours()}`,
|
|
13
|
+
`agent: ${setup.agent}, model: ${setup.model === '' ? 'default' : setup.model}, effort: ${setup.effort === '' ? 'default' : setup.effort}`,
|
|
14
|
+
`wishes came true: ${jobs.text()}`,
|
|
15
|
+
`wishes remained in ${setup.wishes.startsWith(`${homedir()}${sep}`) ? `~${setup.wishes.slice(homedir().length)}` : setup.wishes}: ${existsSync(setup.wishes) ? readdirSync(setup.wishes, { recursive: true, withFileTypes: true }).filter((f) => f.isFile() && f.name.endsWith('.md')).length : 0}`,
|
|
16
|
+
''
|
|
17
|
+
].join('\n')
|
|
18
|
+
});
|
package/src/throttled.js
CHANGED
|
@@ -2,33 +2,28 @@
|
|
|
2
2
|
// SPDX-License-Identifier: MIT
|
|
3
3
|
|
|
4
4
|
import { setTimeout } from 'node:timers/promises';
|
|
5
|
-
import {
|
|
5
|
+
import { duration } from './duration.js';
|
|
6
6
|
|
|
7
|
-
export
|
|
8
|
-
|
|
9
|
-
this.origin = origin;
|
|
10
|
-
this.quota = quota;
|
|
11
|
-
this.throttle = throttle;
|
|
12
|
-
this.interval = interval;
|
|
13
|
-
this.notice = notice;
|
|
14
|
-
}
|
|
15
|
-
async next(signal) {
|
|
16
|
-
await this.#wait(signal, true);
|
|
17
|
-
return this.origin.next(signal);
|
|
18
|
-
}
|
|
19
|
-
async #wait(signal, first) {
|
|
7
|
+
export const throttled = (origin, quota, throttle, interval, notice) => {
|
|
8
|
+
const wait = async (signal, first) => {
|
|
20
9
|
signal.throwIfAborted();
|
|
21
|
-
const left =
|
|
22
|
-
if (left >=
|
|
10
|
+
const left = quota.left(Date.now());
|
|
11
|
+
if (left >= throttle && first) {
|
|
23
12
|
return;
|
|
24
13
|
}
|
|
25
|
-
const share = `${Math.round(left)}% of the ${
|
|
26
|
-
if (left >=
|
|
27
|
-
|
|
14
|
+
const share = `${Math.round(left)}% of the ${quota.name(Date.now())} quota is left`;
|
|
15
|
+
if (left >= throttle) {
|
|
16
|
+
notice.warn(`now ${share}, not below ${throttle}%, new jobs resume`);
|
|
28
17
|
return;
|
|
29
18
|
}
|
|
30
|
-
|
|
31
|
-
await setTimeout(
|
|
32
|
-
await
|
|
33
|
-
}
|
|
34
|
-
|
|
19
|
+
notice.warn(`${first ? '' : 'still '}only ${share}, below ${throttle}%, new jobs wait, next check in ${duration(interval).phrase()}`);
|
|
20
|
+
await setTimeout(interval, undefined, { signal });
|
|
21
|
+
await wait(signal, false);
|
|
22
|
+
};
|
|
23
|
+
return Object.freeze({
|
|
24
|
+
next: async (signal) => {
|
|
25
|
+
await wait(signal, true);
|
|
26
|
+
return origin.next(signal);
|
|
27
|
+
}
|
|
28
|
+
});
|
|
29
|
+
};
|
package/src/tokens.js
CHANGED
|
@@ -1,29 +1,27 @@
|
|
|
1
1
|
// SPDX-FileCopyrightText: Copyright (c) 2026 Yegor Bugayenko
|
|
2
2
|
// SPDX-License-Identifier: MIT
|
|
3
3
|
|
|
4
|
-
export
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
4
|
+
export const tokens = () => {
|
|
5
|
+
const messages = new Map();
|
|
6
|
+
return Object.freeze({
|
|
7
|
+
add: (line) => {
|
|
8
|
+
try {
|
|
9
|
+
const json = JSON.parse(line);
|
|
10
|
+
if (json.type === 'assistant' && json.message?.id !== undefined && json.message?.usage !== undefined) {
|
|
11
|
+
messages.set(
|
|
12
|
+
json.message.id,
|
|
13
|
+
['input_tokens', 'output_tokens', 'cache_read_input_tokens', 'cache_creation_input_tokens']
|
|
14
|
+
.reduce((sum, key) => sum + (json.message.usage[key] ?? 0), 0)
|
|
15
|
+
);
|
|
16
|
+
}
|
|
17
|
+
if (json.type === 'turn.completed' && json.usage !== undefined) {
|
|
18
|
+
messages.set(Symbol('turn'), (json.usage.input_tokens ?? 0) + (json.usage.output_tokens ?? 0));
|
|
19
|
+
}
|
|
20
|
+
} catch {
|
|
21
|
+
return;
|
|
17
22
|
}
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
}
|
|
24
|
-
}
|
|
25
|
-
text() {
|
|
26
|
-
return new Intl.NumberFormat('en', { notation: 'compact', maximumFractionDigits: 1 })
|
|
27
|
-
.format([...this.messages.values()].reduce((sum, count) => sum + count, 0));
|
|
28
|
-
}
|
|
29
|
-
}
|
|
23
|
+
},
|
|
24
|
+
text: () => new Intl.NumberFormat('en', { notation: 'compact', maximumFractionDigits: 1 })
|
|
25
|
+
.format([...messages.values()].reduce((sum, count) => sum + count, 0))
|
|
26
|
+
});
|
|
27
|
+
};
|