@vibe-cafe/vibe-usage 0.10.1 → 0.10.2
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/package.json +1 -1
- package/src/api.js +16 -8
- package/src/client-meta.js +44 -0
- package/src/daemon.js +1 -1
- package/src/sync.js +10 -1
package/package.json
CHANGED
package/src/api.js
CHANGED
|
@@ -14,7 +14,7 @@ export async function ingest(apiUrl, apiKey, buckets, opts, sessions) {
|
|
|
14
14
|
let lastError;
|
|
15
15
|
for (let attempt = 0; attempt < MAX_RETRIES; attempt++) {
|
|
16
16
|
try {
|
|
17
|
-
return await _send(apiUrl, apiKey, buckets, opts
|
|
17
|
+
return await _send(apiUrl, apiKey, buckets, opts, sessions);
|
|
18
18
|
} catch (err) {
|
|
19
19
|
lastError = err;
|
|
20
20
|
// Don't retry auth errors or client errors
|
|
@@ -30,14 +30,22 @@ export async function ingest(apiUrl, apiKey, buckets, opts, sessions) {
|
|
|
30
30
|
throw lastError;
|
|
31
31
|
}
|
|
32
32
|
|
|
33
|
-
function
|
|
33
|
+
export function encodeIngestBody(buckets, opts, sessions) {
|
|
34
|
+
const payload = { buckets };
|
|
35
|
+
if (sessions && sessions.length > 0) payload.sessions = sessions;
|
|
36
|
+
if (opts?.client) payload.client = opts.client;
|
|
37
|
+
const raw = Buffer.from(JSON.stringify(payload));
|
|
38
|
+
const useGzip = raw.length >= GZIP_MIN_BYTES;
|
|
39
|
+
return {
|
|
40
|
+
body: useGzip ? gzipSync(raw) : raw,
|
|
41
|
+
useGzip,
|
|
42
|
+
};
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
function _send(apiUrl, apiKey, buckets, opts, sessions) {
|
|
34
46
|
return new Promise((resolve, reject) => {
|
|
35
47
|
const url = new URL('/api/usage/ingest', apiUrl);
|
|
36
|
-
const
|
|
37
|
-
if (sessions && sessions.length > 0) payload.sessions = sessions;
|
|
38
|
-
const raw = Buffer.from(JSON.stringify(payload));
|
|
39
|
-
const useGzip = raw.length >= GZIP_MIN_BYTES;
|
|
40
|
-
const body = useGzip ? gzipSync(raw) : raw;
|
|
48
|
+
const { body, useGzip } = encodeIngestBody(buckets, opts, sessions);
|
|
41
49
|
const totalBytes = body.length;
|
|
42
50
|
const mod = url.protocol === 'https:' ? https : http;
|
|
43
51
|
|
|
@@ -89,7 +97,7 @@ function _send(apiUrl, apiKey, buckets, onProgress, sessions) {
|
|
|
89
97
|
while (ok && sent < totalBytes) {
|
|
90
98
|
const slice = body.subarray(sent, sent + CHUNK);
|
|
91
99
|
sent += slice.length;
|
|
92
|
-
if (onProgress) onProgress(sent, totalBytes);
|
|
100
|
+
if (opts?.onProgress) opts.onProgress(sent, totalBytes);
|
|
93
101
|
ok = req.write(slice);
|
|
94
102
|
}
|
|
95
103
|
if (sent < totalBytes) {
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import { readFileSync } from 'node:fs';
|
|
2
|
+
import { randomUUID } from 'node:crypto';
|
|
3
|
+
|
|
4
|
+
const pkg = JSON.parse(
|
|
5
|
+
readFileSync(new URL('../package.json', import.meta.url), 'utf-8'),
|
|
6
|
+
);
|
|
7
|
+
|
|
8
|
+
const SURFACES = new Set(['cli', 'daemon', 'mac-app', 'windows-app']);
|
|
9
|
+
|
|
10
|
+
export const COLLECTOR_VERSION = String(pkg.version);
|
|
11
|
+
|
|
12
|
+
function cleanEnv(value, maxLength = 50) {
|
|
13
|
+
if (typeof value !== 'string') return null;
|
|
14
|
+
const cleaned = value.trim().slice(0, maxLength);
|
|
15
|
+
return cleaned || null;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
export function createSyncClient({ defaultSurface = 'cli', hostname } = {}) {
|
|
19
|
+
const requestedSurface = cleanEnv(process.env.VIBE_USAGE_SURFACE, 30);
|
|
20
|
+
const surface = requestedSurface && SURFACES.has(requestedSurface)
|
|
21
|
+
? requestedSurface
|
|
22
|
+
: defaultSurface;
|
|
23
|
+
const runtime = process.versions.bun ? 'bun' : 'node';
|
|
24
|
+
const runtimeVersion = process.versions.bun || process.versions.node;
|
|
25
|
+
|
|
26
|
+
return {
|
|
27
|
+
collectorVersion: COLLECTOR_VERSION,
|
|
28
|
+
surface,
|
|
29
|
+
surfaceVersion: cleanEnv(process.env.VIBE_USAGE_SURFACE_VERSION) || COLLECTOR_VERSION,
|
|
30
|
+
runtime,
|
|
31
|
+
runtimeVersion,
|
|
32
|
+
platform: process.platform,
|
|
33
|
+
hostname: cleanEnv(hostname, 200) || 'unknown',
|
|
34
|
+
syncId: randomUUID(),
|
|
35
|
+
};
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
export function forBatch(client, batchIndex, batchCount) {
|
|
39
|
+
return {
|
|
40
|
+
...client,
|
|
41
|
+
batchIndex,
|
|
42
|
+
batchCount,
|
|
43
|
+
};
|
|
44
|
+
}
|
package/src/daemon.js
CHANGED
|
@@ -34,7 +34,7 @@ export async function runDaemon() {
|
|
|
34
34
|
// eslint-disable-next-line no-constant-condition
|
|
35
35
|
while (true) {
|
|
36
36
|
try {
|
|
37
|
-
await runSync({ throws: true, quiet: true });
|
|
37
|
+
await runSync({ throws: true, quiet: true, surface: 'daemon' });
|
|
38
38
|
consecutiveAuthFailures = 0;
|
|
39
39
|
} catch (err) {
|
|
40
40
|
if (err.message === 'UNAUTHORIZED') {
|
package/src/sync.js
CHANGED
|
@@ -5,6 +5,7 @@ import {
|
|
|
5
5
|
bucketKey, bucketHash, sessionKey, sessionHash,
|
|
6
6
|
} from './state.js';
|
|
7
7
|
import { ingest, fetchSettings } from './api.js';
|
|
8
|
+
import { createSyncClient, forBatch } from './client-meta.js';
|
|
8
9
|
import { parsers } from './parsers/index.js';
|
|
9
10
|
import { success, failure, arrow, link, dim } from './output.js';
|
|
10
11
|
|
|
@@ -17,7 +18,7 @@ function formatBytes(bytes) {
|
|
|
17
18
|
return `${(bytes / (1024 * 1024)).toFixed(1)}MB`;
|
|
18
19
|
}
|
|
19
20
|
|
|
20
|
-
export async function runSync({ throws = false, quiet = false } = {}) {
|
|
21
|
+
export async function runSync({ throws = false, quiet = false, surface = 'cli' } = {}) {
|
|
21
22
|
const config = loadConfig();
|
|
22
23
|
if (!config?.apiKey) {
|
|
23
24
|
console.error(failure('尚未配置,请先运行 `npx @vibe-cafe/vibe-usage init`。'));
|
|
@@ -192,10 +193,12 @@ export async function runSync({ throws = false, quiet = false } = {}) {
|
|
|
192
193
|
let totalDroppedBuckets = 0;
|
|
193
194
|
let totalDroppedUnknownModels = 0;
|
|
194
195
|
let totalDroppedImplausible = 0;
|
|
196
|
+
let totalProtectedBuckets = 0;
|
|
195
197
|
const droppedSources = new Set();
|
|
196
198
|
const bucketBatches = Math.ceil(allBucketsToSend.length / BATCH_SIZE);
|
|
197
199
|
const sessionBatches = Math.ceil(allSessionsToSend.length / SESSION_BATCH_SIZE);
|
|
198
200
|
const totalBatches = Math.max(bucketBatches, sessionBatches, 1);
|
|
201
|
+
const syncClient = createSyncClient({ defaultSurface: surface, hostname: host });
|
|
199
202
|
|
|
200
203
|
try {
|
|
201
204
|
for (let batchIdx = 0; batchIdx < totalBatches; batchIdx++) {
|
|
@@ -205,6 +208,7 @@ export async function runSync({ throws = false, quiet = false } = {}) {
|
|
|
205
208
|
const prefix = totalBatches > 1 ? ` ${dim(`[${batchNum}/${totalBatches}]`)} 上传中 ` : ' 上传中 ';
|
|
206
209
|
|
|
207
210
|
const result = await ingest(apiUrl, config.apiKey, batch, {
|
|
211
|
+
client: forBatch(syncClient, batchIdx, totalBatches),
|
|
208
212
|
onProgress(sent, total) {
|
|
209
213
|
const pct = Math.round((sent / total) * 100);
|
|
210
214
|
process.stdout.write(`\r${prefix}${dim(`${formatBytes(sent)}/${formatBytes(total)} (${pct}%)`)}\x1b[K`);
|
|
@@ -219,6 +223,7 @@ export async function runSync({ throws = false, quiet = false } = {}) {
|
|
|
219
223
|
totalDroppedImplausible += Number(result.dropped.implausible) || 0;
|
|
220
224
|
for (const s of result.dropped.unknownSources || []) droppedSources.add(s);
|
|
221
225
|
}
|
|
226
|
+
totalProtectedBuckets += Number(result.protected?.buckets) || 0;
|
|
222
227
|
|
|
223
228
|
// Commit only this batch's hashes, only after it uploaded successfully.
|
|
224
229
|
// A batch that throws aborts the loop with its keys still absent from
|
|
@@ -259,6 +264,10 @@ export async function runSync({ throws = false, quiet = false } = {}) {
|
|
|
259
264
|
console.log(dim(` ${totalDroppedBuckets} buckets dropped (${reasons.join(';')})`));
|
|
260
265
|
}
|
|
261
266
|
|
|
267
|
+
if (totalProtectedBuckets > 0) {
|
|
268
|
+
console.log(dim(` 服务端保留了 ${totalProtectedBuckets} 个更大的已有 bucket(本次较小快照未覆盖)`));
|
|
269
|
+
}
|
|
270
|
+
|
|
262
271
|
if (!quiet && totalSessionsSynced > 0) {
|
|
263
272
|
const totalActive = allSessionsToSend.reduce((s, x) => s + x.activeSeconds, 0);
|
|
264
273
|
const totalDuration = allSessionsToSend.reduce((s, x) => s + x.durationSeconds, 0);
|