@p4r4d0xb0x/opencode-provider-logger 1.0.1 → 1.1.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.js +419 -5
- package/package.json +5 -6
- package/dist/adapters/claude-code/hook.d.ts +0 -2
- package/dist/adapters/claude-code/hook.js +0 -48
- package/dist/adapters/claude-code/install.d.ts +0 -2
- package/dist/adapters/claude-code/install.js +0 -106
- package/dist/adapters/claude-code/transform.d.ts +0 -26
- package/dist/adapters/claude-code/transform.js +0 -66
- package/dist/adapters/codex-cli/hook.d.ts +0 -2
- package/dist/adapters/codex-cli/hook.js +0 -48
- package/dist/adapters/codex-cli/install.d.ts +0 -2
- package/dist/adapters/codex-cli/install.js +0 -118
- package/dist/adapters/codex-cli/transform.d.ts +0 -26
- package/dist/adapters/codex-cli/transform.js +0 -35
- package/dist/adapters/opencode/hooks.js +0 -87
- package/dist/adapters/opencode/index.js +0 -63
- package/dist/buffer.d.ts +0 -13
- package/dist/buffer.js +0 -45
- package/dist/core/buffer.d.ts +0 -13
- package/dist/core/buffer.js +0 -45
- package/dist/core/entry.d.ts +0 -5
- package/dist/core/entry.js +0 -19
- package/dist/core/index.d.ts +0 -8
- package/dist/core/index.js +0 -5
- package/dist/core/logger.d.ts +0 -21
- package/dist/core/logger.js +0 -107
- package/dist/core/types.d.ts +0 -42
- package/dist/core/types.js +0 -27
- package/dist/core/uploader.d.ts +0 -12
- package/dist/core/uploader.js +0 -45
- package/dist/hooks.d.ts +0 -7
- package/dist/hooks.js +0 -72
- package/dist/logger.d.ts +0 -17
- package/dist/logger.js +0 -97
- package/dist/types.d.ts +0 -21
- package/dist/types.js +0 -14
- package/dist/uploader.d.ts +0 -13
- package/dist/uploader.js +0 -45
package/dist/core/logger.js
DELETED
|
@@ -1,107 +0,0 @@
|
|
|
1
|
-
import { mkdir, writeFile, appendFile, readdir, stat, unlink, rmdir, } from "node:fs/promises";
|
|
2
|
-
import { join } from "node:path";
|
|
3
|
-
export class LocalWriter {
|
|
4
|
-
config;
|
|
5
|
-
constructor(config) {
|
|
6
|
-
this.config = config;
|
|
7
|
-
}
|
|
8
|
-
sessionDir(sessionID) {
|
|
9
|
-
return join(this.config.localDir, sessionID);
|
|
10
|
-
}
|
|
11
|
-
/** Write a batch of entries to a new JSONL file (used by buffered mode) */
|
|
12
|
-
async write(sessionID, entries) {
|
|
13
|
-
if (entries.length === 0)
|
|
14
|
-
return "";
|
|
15
|
-
const dir = this.sessionDir(sessionID);
|
|
16
|
-
await mkdir(dir, { recursive: true });
|
|
17
|
-
const tsNanos = makeTimestampNanos();
|
|
18
|
-
const filepath = join(dir, `${tsNanos}.jsonl`);
|
|
19
|
-
const content = entries.map((e) => JSON.stringify(e)).join("\n") + "\n";
|
|
20
|
-
await writeFile(filepath, content, "utf-8");
|
|
21
|
-
return filepath;
|
|
22
|
-
}
|
|
23
|
-
/** Append a single entry to a session-scoped JSONL file (used by hook scripts) */
|
|
24
|
-
async append(sessionID, entry) {
|
|
25
|
-
const dir = this.sessionDir(sessionID);
|
|
26
|
-
await mkdir(dir, { recursive: true });
|
|
27
|
-
// Use a stable filename per session so repeated hook calls append to one file
|
|
28
|
-
const filepath = join(dir, `${sessionID}.jsonl`);
|
|
29
|
-
await appendFile(filepath, JSON.stringify(entry) + "\n", "utf-8");
|
|
30
|
-
return filepath;
|
|
31
|
-
}
|
|
32
|
-
async getPending() {
|
|
33
|
-
const pending = [];
|
|
34
|
-
try {
|
|
35
|
-
const sessions = await readdir(this.config.localDir);
|
|
36
|
-
for (const sessionID of sessions) {
|
|
37
|
-
const dir = this.sessionDir(sessionID);
|
|
38
|
-
const s = await stat(dir).catch(() => null);
|
|
39
|
-
if (!s?.isDirectory())
|
|
40
|
-
continue;
|
|
41
|
-
const files = await readdir(dir);
|
|
42
|
-
const uploaded = new Set(files.filter((f) => f.endsWith(".uploaded")));
|
|
43
|
-
for (const file of files) {
|
|
44
|
-
if (file.endsWith(".jsonl") &&
|
|
45
|
-
!uploaded.has(`${file}.uploaded`)) {
|
|
46
|
-
pending.push({
|
|
47
|
-
path: join(dir, file),
|
|
48
|
-
sessionID,
|
|
49
|
-
tsNanos: file.replace(".jsonl", ""),
|
|
50
|
-
});
|
|
51
|
-
}
|
|
52
|
-
}
|
|
53
|
-
}
|
|
54
|
-
}
|
|
55
|
-
catch {
|
|
56
|
-
// directory may not exist yet
|
|
57
|
-
}
|
|
58
|
-
return pending;
|
|
59
|
-
}
|
|
60
|
-
async markUploaded(filepath) {
|
|
61
|
-
await writeFile(`${filepath}.uploaded`, "", "utf-8");
|
|
62
|
-
}
|
|
63
|
-
async cleanup() {
|
|
64
|
-
const cutoff = Date.now() - this.config.retentionDays * 24 * 60 * 60 * 1000;
|
|
65
|
-
try {
|
|
66
|
-
const sessions = await readdir(this.config.localDir);
|
|
67
|
-
for (const sessionID of sessions) {
|
|
68
|
-
const dir = this.sessionDir(sessionID);
|
|
69
|
-
const s = await stat(dir).catch(() => null);
|
|
70
|
-
if (!s?.isDirectory())
|
|
71
|
-
continue;
|
|
72
|
-
const files = await readdir(dir);
|
|
73
|
-
for (const file of files) {
|
|
74
|
-
if (!file.endsWith(".jsonl"))
|
|
75
|
-
continue;
|
|
76
|
-
const filepath = join(dir, file);
|
|
77
|
-
const fstat = await stat(filepath).catch(() => null);
|
|
78
|
-
if (!fstat)
|
|
79
|
-
continue;
|
|
80
|
-
const hasMarker = files.includes(`${file}.uploaded`);
|
|
81
|
-
if (hasMarker && fstat.mtimeMs < cutoff) {
|
|
82
|
-
await unlink(filepath).catch(() => { });
|
|
83
|
-
await unlink(`${filepath}.uploaded`).catch(() => { });
|
|
84
|
-
}
|
|
85
|
-
}
|
|
86
|
-
// Remove empty session directories
|
|
87
|
-
const remaining = await readdir(dir).catch(() => ["placeholder"]);
|
|
88
|
-
if (remaining.length === 0) {
|
|
89
|
-
await rmdir(dir).catch(() => { });
|
|
90
|
-
}
|
|
91
|
-
}
|
|
92
|
-
}
|
|
93
|
-
catch {
|
|
94
|
-
// ignore
|
|
95
|
-
}
|
|
96
|
-
}
|
|
97
|
-
}
|
|
98
|
-
let _counter = 0;
|
|
99
|
-
export function makeTimestampNanos() {
|
|
100
|
-
const ms = Date.now();
|
|
101
|
-
const seq = _counter++;
|
|
102
|
-
return `${ms}${String(seq % 1_000_000).padStart(6, "0")}`;
|
|
103
|
-
}
|
|
104
|
-
/** @internal — exposed for testing */
|
|
105
|
-
export function _resetCounter() {
|
|
106
|
-
_counter = 0;
|
|
107
|
-
}
|
package/dist/core/types.d.ts
DELETED
|
@@ -1,42 +0,0 @@
|
|
|
1
|
-
/** Supported platforms */
|
|
2
|
-
export type Platform = "opencode" | "claude-code" | "codex-cli";
|
|
3
|
-
/** Normalised event categories across all platforms */
|
|
4
|
-
export type EventCategory = "session.start" | "session.end" | "session.idle" | "session.compact" | "session.status" | "session.created" | "prompt.user" | "prompt.system" | "prompt.params" | "message.assistant" | "message.updated" | "tool.pre" | "tool.post" | "tool.error" | "permission.request" | "permission.denied" | "subagent.start" | "subagent.stop" | "task.created" | "task.completed" | "config.change" | "file.change" | "cwd.change" | "instructions.loaded" | "notification" | "elicitation" | "elicitation.result" | "raw";
|
|
5
|
-
/** A single log entry — the universal record written to JSONL */
|
|
6
|
-
export interface ProviderLogEntry {
|
|
7
|
-
id: string;
|
|
8
|
-
timestamp: string;
|
|
9
|
-
timestampNanos: string;
|
|
10
|
-
sessionID: string;
|
|
11
|
-
platform: Platform;
|
|
12
|
-
category: EventCategory;
|
|
13
|
-
/** Original platform-specific hook/event name (for debugging) */
|
|
14
|
-
hookName: string;
|
|
15
|
-
data: {
|
|
16
|
-
input: unknown;
|
|
17
|
-
output: unknown;
|
|
18
|
-
};
|
|
19
|
-
meta?: {
|
|
20
|
-
model?: string;
|
|
21
|
-
toolName?: string;
|
|
22
|
-
turnID?: string;
|
|
23
|
-
agentID?: string;
|
|
24
|
-
agentType?: string;
|
|
25
|
-
transcriptPath?: string;
|
|
26
|
-
cwd?: string;
|
|
27
|
-
userID?: string;
|
|
28
|
-
permissionMode?: string;
|
|
29
|
-
};
|
|
30
|
-
}
|
|
31
|
-
export type OpenCodeHookName = "chat.message" | "chat.params" | "experimental.chat.system.transform" | "experimental.chat.messages.transform" | "experimental.text.complete" | "tool.execute.before" | "tool.execute.after" | "event";
|
|
32
|
-
export interface PluginConfig {
|
|
33
|
-
localDir: string;
|
|
34
|
-
r2Remote: string;
|
|
35
|
-
retentionDays: number;
|
|
36
|
-
flushThresholdBytes: number;
|
|
37
|
-
}
|
|
38
|
-
export declare function defaultConfig(platform: Platform): PluginConfig;
|
|
39
|
-
/** @deprecated Use defaultConfig("opencode") instead */
|
|
40
|
-
export declare const DEFAULT_CONFIG: PluginConfig;
|
|
41
|
-
/** OpenCode event types worth logging */
|
|
42
|
-
export declare const LOG_EVENT_TYPES: Set<string>;
|
package/dist/core/types.js
DELETED
|
@@ -1,27 +0,0 @@
|
|
|
1
|
-
// ---------------------------------------------------------------------------
|
|
2
|
-
// Platform-neutral provider log types
|
|
3
|
-
// ---------------------------------------------------------------------------
|
|
4
|
-
export function defaultConfig(platform) {
|
|
5
|
-
const home = process.env.HOME ?? require("node:os").homedir();
|
|
6
|
-
const dirMap = {
|
|
7
|
-
opencode: `${home}/.cache/opencode/provider-logs`,
|
|
8
|
-
"claude-code": `${home}/.cache/claude-code/provider-logs`,
|
|
9
|
-
"codex-cli": `${home}/.cache/codex-cli/provider-logs`,
|
|
10
|
-
};
|
|
11
|
-
return {
|
|
12
|
-
localDir: dirMap[platform],
|
|
13
|
-
r2Remote: "paradox:ailog",
|
|
14
|
-
retentionDays: 7,
|
|
15
|
-
flushThresholdBytes: 10 * 1024 * 1024, // 10 MB
|
|
16
|
-
};
|
|
17
|
-
}
|
|
18
|
-
/** @deprecated Use defaultConfig("opencode") instead */
|
|
19
|
-
export const DEFAULT_CONFIG = defaultConfig("opencode");
|
|
20
|
-
/** OpenCode event types worth logging */
|
|
21
|
-
export const LOG_EVENT_TYPES = new Set([
|
|
22
|
-
"message.updated",
|
|
23
|
-
"session.idle",
|
|
24
|
-
"session.status",
|
|
25
|
-
"session.created",
|
|
26
|
-
"session.compacted",
|
|
27
|
-
]);
|
package/dist/core/uploader.d.ts
DELETED
|
@@ -1,12 +0,0 @@
|
|
|
1
|
-
import type { PluginConfig } from "./types.js";
|
|
2
|
-
import type { LocalWriter, PendingFile } from "./logger.js";
|
|
3
|
-
export type LogFn = (level: "debug" | "info" | "warn" | "error", message: string, extra?: Record<string, unknown>) => void;
|
|
4
|
-
export declare class R2Uploader {
|
|
5
|
-
private config;
|
|
6
|
-
private writer;
|
|
7
|
-
private log;
|
|
8
|
-
constructor(config: PluginConfig, writer: LocalWriter, log: LogFn);
|
|
9
|
-
upload(file: PendingFile): Promise<boolean>;
|
|
10
|
-
retryPending(): Promise<void>;
|
|
11
|
-
cleanup(): Promise<void>;
|
|
12
|
-
}
|
package/dist/core/uploader.js
DELETED
|
@@ -1,45 +0,0 @@
|
|
|
1
|
-
import { execFile } from "node:child_process";
|
|
2
|
-
import { promisify } from "node:util";
|
|
3
|
-
const execFileAsync = promisify(execFile);
|
|
4
|
-
export class R2Uploader {
|
|
5
|
-
config;
|
|
6
|
-
writer;
|
|
7
|
-
log;
|
|
8
|
-
constructor(config, writer, log) {
|
|
9
|
-
this.config = config;
|
|
10
|
-
this.writer = writer;
|
|
11
|
-
this.log = log;
|
|
12
|
-
}
|
|
13
|
-
async upload(file) {
|
|
14
|
-
const remotePath = `${this.config.r2Remote}/${file.sessionID}/${file.tsNanos}.jsonl`;
|
|
15
|
-
try {
|
|
16
|
-
await execFileAsync("rclone", [
|
|
17
|
-
"copyto",
|
|
18
|
-
file.path,
|
|
19
|
-
remotePath,
|
|
20
|
-
"--no-traverse",
|
|
21
|
-
]);
|
|
22
|
-
await this.writer.markUploaded(file.path);
|
|
23
|
-
this.log("info", `Uploaded → ${remotePath}`);
|
|
24
|
-
return true;
|
|
25
|
-
}
|
|
26
|
-
catch (err) {
|
|
27
|
-
this.log("warn", `Upload failed: ${file.path}`, {
|
|
28
|
-
error: String(err),
|
|
29
|
-
});
|
|
30
|
-
return false;
|
|
31
|
-
}
|
|
32
|
-
}
|
|
33
|
-
async retryPending() {
|
|
34
|
-
const pending = await this.writer.getPending();
|
|
35
|
-
if (pending.length === 0)
|
|
36
|
-
return;
|
|
37
|
-
this.log("info", `Retrying ${pending.length} pending upload(s)`);
|
|
38
|
-
for (const file of pending) {
|
|
39
|
-
await this.upload(file);
|
|
40
|
-
}
|
|
41
|
-
}
|
|
42
|
-
async cleanup() {
|
|
43
|
-
await this.writer.cleanup();
|
|
44
|
-
}
|
|
45
|
-
}
|
package/dist/hooks.d.ts
DELETED
|
@@ -1,7 +0,0 @@
|
|
|
1
|
-
import type { Hooks } from "@opencode-ai/plugin";
|
|
2
|
-
import type { SessionBuffer } from "./buffer.js";
|
|
3
|
-
import type { PluginConfig } from "./types.js";
|
|
4
|
-
type FlushFn = (sessionID: string) => Promise<void>;
|
|
5
|
-
type LogFn = (level: "error" | "warn", message: string, extra?: Record<string, unknown>) => void;
|
|
6
|
-
export declare function createHooks(buffer: SessionBuffer, config: PluginConfig, flush: FlushFn, log?: LogFn): Hooks;
|
|
7
|
-
export {};
|
package/dist/hooks.js
DELETED
|
@@ -1,72 +0,0 @@
|
|
|
1
|
-
import { LOG_EVENT_TYPES } from "./types.js";
|
|
2
|
-
import { randomUUID } from "node:crypto";
|
|
3
|
-
function makeEntry(sessionID, hook, input, output) {
|
|
4
|
-
const now = Date.now();
|
|
5
|
-
const micro = Math.floor(Math.random() * 1_000_000);
|
|
6
|
-
return {
|
|
7
|
-
id: randomUUID(),
|
|
8
|
-
timestamp: new Date(now).toISOString(),
|
|
9
|
-
timestampNanos: `${now}${String(micro).padStart(6, "0")}`,
|
|
10
|
-
sessionID,
|
|
11
|
-
hook,
|
|
12
|
-
data: { input, output },
|
|
13
|
-
};
|
|
14
|
-
}
|
|
15
|
-
export function createHooks(buffer, config, flush, log) {
|
|
16
|
-
const safeRecord = (sessionID, hook, input, output) => {
|
|
17
|
-
try {
|
|
18
|
-
buffer.append(makeEntry(sessionID, hook, input, output));
|
|
19
|
-
if (buffer.sizeOf(sessionID) >= config.flushThresholdBytes) {
|
|
20
|
-
flush(sessionID).catch(() => { });
|
|
21
|
-
}
|
|
22
|
-
}
|
|
23
|
-
catch (err) {
|
|
24
|
-
log?.("error", `Hook record failed [${hook}]`, { error: String(err) });
|
|
25
|
-
}
|
|
26
|
-
};
|
|
27
|
-
return {
|
|
28
|
-
"chat.message": async (input, output) => {
|
|
29
|
-
safeRecord(input.sessionID, "chat.message", input, output);
|
|
30
|
-
},
|
|
31
|
-
"chat.params": async (input, output) => {
|
|
32
|
-
safeRecord(input.sessionID, "chat.params", input, output);
|
|
33
|
-
},
|
|
34
|
-
"experimental.chat.system.transform": async (input, output) => {
|
|
35
|
-
const sessionID = input.sessionID ?? "unknown";
|
|
36
|
-
safeRecord(sessionID, "experimental.chat.system.transform", input, output);
|
|
37
|
-
},
|
|
38
|
-
"experimental.chat.messages.transform": async (input, output) => {
|
|
39
|
-
// input is {} — extract sessionID from first message
|
|
40
|
-
const firstMsg = output.messages?.[0]?.info;
|
|
41
|
-
const sessionID = firstMsg?.sessionID ?? "unknown";
|
|
42
|
-
safeRecord(sessionID, "experimental.chat.messages.transform", input, output);
|
|
43
|
-
},
|
|
44
|
-
"experimental.text.complete": async (input, output) => {
|
|
45
|
-
safeRecord(input.sessionID, "experimental.text.complete", input, output);
|
|
46
|
-
},
|
|
47
|
-
"tool.execute.before": async (input, output) => {
|
|
48
|
-
safeRecord(input.sessionID, "tool.execute.before", input, output);
|
|
49
|
-
},
|
|
50
|
-
"tool.execute.after": async (input, output) => {
|
|
51
|
-
safeRecord(input.sessionID, "tool.execute.after", input, output);
|
|
52
|
-
},
|
|
53
|
-
event: async ({ event }) => {
|
|
54
|
-
try {
|
|
55
|
-
// Each Event is { type: string; properties: { sessionID?, ... } }
|
|
56
|
-
const ev = event;
|
|
57
|
-
if (!LOG_EVENT_TYPES.has(ev.type))
|
|
58
|
-
return;
|
|
59
|
-
const sessionID = ev.properties?.sessionID ?? ev.properties?.info?.sessionID;
|
|
60
|
-
if (!sessionID)
|
|
61
|
-
return;
|
|
62
|
-
safeRecord(sessionID, "event", { type: ev.type }, ev.properties);
|
|
63
|
-
if (ev.type === "session.idle") {
|
|
64
|
-
await flush(sessionID);
|
|
65
|
-
}
|
|
66
|
-
}
|
|
67
|
-
catch (err) {
|
|
68
|
-
log?.("error", "Event hook failed", { error: String(err) });
|
|
69
|
-
}
|
|
70
|
-
},
|
|
71
|
-
};
|
|
72
|
-
}
|
package/dist/logger.d.ts
DELETED
|
@@ -1,17 +0,0 @@
|
|
|
1
|
-
import type { LogEntry, PluginConfig } from "./types.js";
|
|
2
|
-
export interface PendingFile {
|
|
3
|
-
path: string;
|
|
4
|
-
sessionID: string;
|
|
5
|
-
tsNanos: string;
|
|
6
|
-
}
|
|
7
|
-
export declare class LocalWriter {
|
|
8
|
-
private config;
|
|
9
|
-
constructor(config: PluginConfig);
|
|
10
|
-
private sessionDir;
|
|
11
|
-
write(sessionID: string, entries: LogEntry[]): Promise<string>;
|
|
12
|
-
getPending(): Promise<PendingFile[]>;
|
|
13
|
-
markUploaded(filepath: string): Promise<void>;
|
|
14
|
-
cleanup(): Promise<void>;
|
|
15
|
-
}
|
|
16
|
-
/** @internal — exposed for testing */
|
|
17
|
-
export declare function _resetCounter(): void;
|
package/dist/logger.js
DELETED
|
@@ -1,97 +0,0 @@
|
|
|
1
|
-
import { mkdir, writeFile, readdir, stat, unlink, rmdir, } from "node:fs/promises";
|
|
2
|
-
import { join } from "node:path";
|
|
3
|
-
export class LocalWriter {
|
|
4
|
-
config;
|
|
5
|
-
constructor(config) {
|
|
6
|
-
this.config = config;
|
|
7
|
-
}
|
|
8
|
-
sessionDir(sessionID) {
|
|
9
|
-
return join(this.config.localDir, sessionID);
|
|
10
|
-
}
|
|
11
|
-
async write(sessionID, entries) {
|
|
12
|
-
if (entries.length === 0)
|
|
13
|
-
return "";
|
|
14
|
-
const dir = this.sessionDir(sessionID);
|
|
15
|
-
await mkdir(dir, { recursive: true });
|
|
16
|
-
const tsNanos = makeTimestampNanos();
|
|
17
|
-
const filepath = join(dir, `${tsNanos}.jsonl`);
|
|
18
|
-
const content = entries.map((e) => JSON.stringify(e)).join("\n") + "\n";
|
|
19
|
-
await writeFile(filepath, content, "utf-8");
|
|
20
|
-
return filepath;
|
|
21
|
-
}
|
|
22
|
-
async getPending() {
|
|
23
|
-
const pending = [];
|
|
24
|
-
try {
|
|
25
|
-
const sessions = await readdir(this.config.localDir);
|
|
26
|
-
for (const sessionID of sessions) {
|
|
27
|
-
const dir = this.sessionDir(sessionID);
|
|
28
|
-
const s = await stat(dir).catch(() => null);
|
|
29
|
-
if (!s?.isDirectory())
|
|
30
|
-
continue;
|
|
31
|
-
const files = await readdir(dir);
|
|
32
|
-
const uploaded = new Set(files.filter((f) => f.endsWith(".uploaded")));
|
|
33
|
-
for (const file of files) {
|
|
34
|
-
if (file.endsWith(".jsonl") &&
|
|
35
|
-
!uploaded.has(`${file}.uploaded`)) {
|
|
36
|
-
pending.push({
|
|
37
|
-
path: join(dir, file),
|
|
38
|
-
sessionID,
|
|
39
|
-
tsNanos: file.replace(".jsonl", ""),
|
|
40
|
-
});
|
|
41
|
-
}
|
|
42
|
-
}
|
|
43
|
-
}
|
|
44
|
-
}
|
|
45
|
-
catch {
|
|
46
|
-
// directory may not exist yet
|
|
47
|
-
}
|
|
48
|
-
return pending;
|
|
49
|
-
}
|
|
50
|
-
async markUploaded(filepath) {
|
|
51
|
-
await writeFile(`${filepath}.uploaded`, "", "utf-8");
|
|
52
|
-
}
|
|
53
|
-
async cleanup() {
|
|
54
|
-
const cutoff = Date.now() - this.config.retentionDays * 24 * 60 * 60 * 1000;
|
|
55
|
-
try {
|
|
56
|
-
const sessions = await readdir(this.config.localDir);
|
|
57
|
-
for (const sessionID of sessions) {
|
|
58
|
-
const dir = this.sessionDir(sessionID);
|
|
59
|
-
const s = await stat(dir).catch(() => null);
|
|
60
|
-
if (!s?.isDirectory())
|
|
61
|
-
continue;
|
|
62
|
-
const files = await readdir(dir);
|
|
63
|
-
for (const file of files) {
|
|
64
|
-
if (!file.endsWith(".jsonl"))
|
|
65
|
-
continue;
|
|
66
|
-
const filepath = join(dir, file);
|
|
67
|
-
const fstat = await stat(filepath).catch(() => null);
|
|
68
|
-
if (!fstat)
|
|
69
|
-
continue;
|
|
70
|
-
const hasMarker = files.includes(`${file}.uploaded`);
|
|
71
|
-
if (hasMarker && fstat.mtimeMs < cutoff) {
|
|
72
|
-
await unlink(filepath).catch(() => { });
|
|
73
|
-
await unlink(`${filepath}.uploaded`).catch(() => { });
|
|
74
|
-
}
|
|
75
|
-
}
|
|
76
|
-
// Remove empty session directories
|
|
77
|
-
const remaining = await readdir(dir).catch(() => ["placeholder"]);
|
|
78
|
-
if (remaining.length === 0) {
|
|
79
|
-
await rmdir(dir).catch(() => { });
|
|
80
|
-
}
|
|
81
|
-
}
|
|
82
|
-
}
|
|
83
|
-
catch {
|
|
84
|
-
// ignore
|
|
85
|
-
}
|
|
86
|
-
}
|
|
87
|
-
}
|
|
88
|
-
let _counter = 0;
|
|
89
|
-
function makeTimestampNanos() {
|
|
90
|
-
const ms = Date.now();
|
|
91
|
-
const seq = _counter++;
|
|
92
|
-
return `${ms}${String(seq % 1_000_000).padStart(6, "0")}`;
|
|
93
|
-
}
|
|
94
|
-
/** @internal — exposed for testing */
|
|
95
|
-
export function _resetCounter() {
|
|
96
|
-
_counter = 0;
|
|
97
|
-
}
|
package/dist/types.d.ts
DELETED
|
@@ -1,21 +0,0 @@
|
|
|
1
|
-
export interface LogEntry {
|
|
2
|
-
id: string;
|
|
3
|
-
timestamp: string;
|
|
4
|
-
timestampNanos: string;
|
|
5
|
-
sessionID: string;
|
|
6
|
-
hook: HookName;
|
|
7
|
-
data: {
|
|
8
|
-
input: unknown;
|
|
9
|
-
output: unknown;
|
|
10
|
-
};
|
|
11
|
-
}
|
|
12
|
-
export type HookName = "chat.message" | "chat.params" | "experimental.chat.system.transform" | "experimental.chat.messages.transform" | "experimental.text.complete" | "tool.execute.before" | "tool.execute.after" | "event";
|
|
13
|
-
export interface PluginConfig {
|
|
14
|
-
localDir: string;
|
|
15
|
-
r2Remote: string;
|
|
16
|
-
retentionDays: number;
|
|
17
|
-
flushThresholdBytes: number;
|
|
18
|
-
}
|
|
19
|
-
export declare const DEFAULT_CONFIG: PluginConfig;
|
|
20
|
-
/** Event types worth logging for training data */
|
|
21
|
-
export declare const LOG_EVENT_TYPES: Set<string>;
|
package/dist/types.js
DELETED
|
@@ -1,14 +0,0 @@
|
|
|
1
|
-
export const DEFAULT_CONFIG = {
|
|
2
|
-
localDir: `${process.env.HOME ?? require("node:os").homedir()}/.cache/opencode/provider-logs`,
|
|
3
|
-
r2Remote: "paradox:ailog",
|
|
4
|
-
retentionDays: 7,
|
|
5
|
-
flushThresholdBytes: 10 * 1024 * 1024, // 10 MB
|
|
6
|
-
};
|
|
7
|
-
/** Event types worth logging for training data */
|
|
8
|
-
export const LOG_EVENT_TYPES = new Set([
|
|
9
|
-
"message.updated",
|
|
10
|
-
"session.idle",
|
|
11
|
-
"session.status",
|
|
12
|
-
"session.created",
|
|
13
|
-
"session.compacted",
|
|
14
|
-
]);
|
package/dist/uploader.d.ts
DELETED
|
@@ -1,13 +0,0 @@
|
|
|
1
|
-
import type { PluginConfig } from "./types.js";
|
|
2
|
-
import type { LocalWriter, PendingFile } from "./logger.js";
|
|
3
|
-
type LogFn = (level: "debug" | "info" | "warn" | "error", message: string, extra?: Record<string, unknown>) => void;
|
|
4
|
-
export declare class R2Uploader {
|
|
5
|
-
private config;
|
|
6
|
-
private writer;
|
|
7
|
-
private log;
|
|
8
|
-
constructor(config: PluginConfig, writer: LocalWriter, log: LogFn);
|
|
9
|
-
upload(file: PendingFile): Promise<boolean>;
|
|
10
|
-
retryPending(): Promise<void>;
|
|
11
|
-
cleanup(): Promise<void>;
|
|
12
|
-
}
|
|
13
|
-
export {};
|
package/dist/uploader.js
DELETED
|
@@ -1,45 +0,0 @@
|
|
|
1
|
-
import { execFile } from "node:child_process";
|
|
2
|
-
import { promisify } from "node:util";
|
|
3
|
-
const execFileAsync = promisify(execFile);
|
|
4
|
-
export class R2Uploader {
|
|
5
|
-
config;
|
|
6
|
-
writer;
|
|
7
|
-
log;
|
|
8
|
-
constructor(config, writer, log) {
|
|
9
|
-
this.config = config;
|
|
10
|
-
this.writer = writer;
|
|
11
|
-
this.log = log;
|
|
12
|
-
}
|
|
13
|
-
async upload(file) {
|
|
14
|
-
const remotePath = `${this.config.r2Remote}/${file.sessionID}/${file.tsNanos}.jsonl`;
|
|
15
|
-
try {
|
|
16
|
-
await execFileAsync("rclone", [
|
|
17
|
-
"copyto",
|
|
18
|
-
file.path,
|
|
19
|
-
remotePath,
|
|
20
|
-
"--no-traverse",
|
|
21
|
-
]);
|
|
22
|
-
await this.writer.markUploaded(file.path);
|
|
23
|
-
this.log("info", `Uploaded → ${remotePath}`);
|
|
24
|
-
return true;
|
|
25
|
-
}
|
|
26
|
-
catch (err) {
|
|
27
|
-
this.log("warn", `Upload failed: ${file.path}`, {
|
|
28
|
-
error: String(err),
|
|
29
|
-
});
|
|
30
|
-
return false;
|
|
31
|
-
}
|
|
32
|
-
}
|
|
33
|
-
async retryPending() {
|
|
34
|
-
const pending = await this.writer.getPending();
|
|
35
|
-
if (pending.length === 0)
|
|
36
|
-
return;
|
|
37
|
-
this.log("info", `Retrying ${pending.length} pending upload(s)`);
|
|
38
|
-
for (const file of pending) {
|
|
39
|
-
await this.upload(file);
|
|
40
|
-
}
|
|
41
|
-
}
|
|
42
|
-
async cleanup() {
|
|
43
|
-
await this.writer.cleanup();
|
|
44
|
-
}
|
|
45
|
-
}
|