chore: improve storage/ implementation; use log(...) consistently (#473)
This PR tidies up primitives under storage/. **Noop changes:** * Promote logger implementation to top-level utility outside of agent/ * Use logger within storage primitives * Cleanup doc strings and comments **Functional changes:** * Increase command history size to 10_000 * Remove unnecessary debounce implementation and ensure a session ID is created only once per agent loop --------- Signed-off-by: Thibault Sottiaux <tibo@openai.com>
This commit is contained in:
committed by
GitHub
parent
8f1ea7fa85
commit
dc276999a9
@@ -10,8 +10,8 @@ import type {
|
||||
} from "openai/resources/responses/responses.mjs";
|
||||
import type { Reasoning } from "openai/resources.mjs";
|
||||
|
||||
import { log } from "./log.js";
|
||||
import { OPENAI_TIMEOUT_MS, getApiKey, getBaseUrl } from "../config.js";
|
||||
import { log } from "../logger/log.js";
|
||||
import { parseToolCallArguments } from "../parsers.js";
|
||||
import { responsesCreateViaChatCompletions } from "../responses.js";
|
||||
import {
|
||||
|
||||
@@ -5,12 +5,12 @@ import type { ApplyPatchCommand, ApprovalPolicy } from "../../approvals.js";
|
||||
import type { ResponseInputItem } from "openai/resources/responses/responses.mjs";
|
||||
|
||||
import { exec, execApplyPatch } from "./exec.js";
|
||||
import { isLoggingEnabled, log } from "./log.js";
|
||||
import { ReviewDecision } from "./review.js";
|
||||
import { FullAutoErrorMode } from "../auto-approval-mode.js";
|
||||
import { SandboxType } from "./sandbox/interface.js";
|
||||
import { canAutoApprove } from "../../approvals.js";
|
||||
import { formatCommandForDisplay } from "../../format-command.js";
|
||||
import { isLoggingEnabled, log } from "../logger/log.js";
|
||||
import { access } from "fs/promises";
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
@@ -1,137 +0,0 @@
|
||||
import * as fsSync from "fs";
|
||||
import * as fs from "fs/promises";
|
||||
import * as os from "os";
|
||||
import * as path from "path";
|
||||
|
||||
interface Logger {
|
||||
/** Checking this can be used to avoid constructing a large log message. */
|
||||
isLoggingEnabled(): boolean;
|
||||
|
||||
log(message: string): void;
|
||||
}
|
||||
|
||||
class AsyncLogger implements Logger {
|
||||
private queue: Array<string> = [];
|
||||
private isWriting: boolean = false;
|
||||
|
||||
constructor(private filePath: string) {
|
||||
this.filePath = filePath;
|
||||
}
|
||||
|
||||
isLoggingEnabled(): boolean {
|
||||
return true;
|
||||
}
|
||||
|
||||
log(message: string): void {
|
||||
const entry = `[${now()}] ${message}\n`;
|
||||
this.queue.push(entry);
|
||||
this.maybeWrite();
|
||||
}
|
||||
|
||||
private async maybeWrite(): Promise<void> {
|
||||
if (this.isWriting || this.queue.length === 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.isWriting = true;
|
||||
const messages = this.queue.join("");
|
||||
this.queue = [];
|
||||
|
||||
try {
|
||||
await fs.appendFile(this.filePath, messages);
|
||||
} finally {
|
||||
this.isWriting = false;
|
||||
}
|
||||
|
||||
this.maybeWrite();
|
||||
}
|
||||
}
|
||||
|
||||
class EmptyLogger implements Logger {
|
||||
isLoggingEnabled(): boolean {
|
||||
return false;
|
||||
}
|
||||
|
||||
log(_message: string): void {
|
||||
// No-op
|
||||
}
|
||||
}
|
||||
|
||||
function now() {
|
||||
const date = new Date();
|
||||
const year = date.getFullYear();
|
||||
const month = String(date.getMonth() + 1).padStart(2, "0");
|
||||
const day = String(date.getDate()).padStart(2, "0");
|
||||
const hours = String(date.getHours()).padStart(2, "0");
|
||||
const minutes = String(date.getMinutes()).padStart(2, "0");
|
||||
const seconds = String(date.getSeconds()).padStart(2, "0");
|
||||
return `${year}-${month}-${day}T${hours}:${minutes}:${seconds}`;
|
||||
}
|
||||
|
||||
let logger: Logger;
|
||||
|
||||
/**
|
||||
* Creates a .log file for this session, but also symlinks codex-cli-latest.log
|
||||
* to the current log file so you can reliably run:
|
||||
*
|
||||
* - Mac/Windows: `tail -F "$TMPDIR/oai-codex/codex-cli-latest.log"`
|
||||
* - Linux: `tail -F ~/.local/oai-codex/codex-cli-latest.log`
|
||||
*/
|
||||
export function initLogger(): Logger {
|
||||
if (logger) {
|
||||
return logger;
|
||||
} else if (!process.env["DEBUG"]) {
|
||||
logger = new EmptyLogger();
|
||||
return logger;
|
||||
}
|
||||
|
||||
const isMac = process.platform === "darwin";
|
||||
const isWin = process.platform === "win32";
|
||||
|
||||
// On Mac and Windows, os.tmpdir() returns a user-specific folder, so prefer
|
||||
// it there. On Linux, use ~/.local/oai-codex so logs are not world-readable.
|
||||
const logDir =
|
||||
isMac || isWin
|
||||
? path.join(os.tmpdir(), "oai-codex")
|
||||
: path.join(os.homedir(), ".local", "oai-codex");
|
||||
fsSync.mkdirSync(logDir, { recursive: true });
|
||||
const logFile = path.join(logDir, `codex-cli-${now()}.log`);
|
||||
// Write the empty string so the file exists and can be tail'd.
|
||||
fsSync.writeFileSync(logFile, "");
|
||||
|
||||
// Symlink to codex-cli-latest.log on UNIX because Windows is funny about
|
||||
// symlinks.
|
||||
if (!isWin) {
|
||||
const latestLink = path.join(logDir, "codex-cli-latest.log");
|
||||
try {
|
||||
fsSync.symlinkSync(logFile, latestLink, "file");
|
||||
} catch (err: unknown) {
|
||||
const error = err as NodeJS.ErrnoException;
|
||||
if (error.code === "EEXIST") {
|
||||
fsSync.unlinkSync(latestLink);
|
||||
fsSync.symlinkSync(logFile, latestLink, "file");
|
||||
} else {
|
||||
throw err;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
logger = new AsyncLogger(logFile);
|
||||
return logger;
|
||||
}
|
||||
|
||||
export function log(message: string): void {
|
||||
(logger ?? initLogger()).log(message);
|
||||
}
|
||||
|
||||
/**
|
||||
* USE SPARINGLY! This function should only be used to guard a call to log() if
|
||||
* the log message is large and you want to avoid constructing it if logging is
|
||||
* disabled.
|
||||
*
|
||||
* `log()` is already a no-op if DEBUG is not set, so an extra
|
||||
* `isLoggingEnabled()` check is unnecessary.
|
||||
*/
|
||||
export function isLoggingEnabled(): boolean {
|
||||
return (logger ?? initLogger()).isLoggingEnabled();
|
||||
}
|
||||
@@ -2,7 +2,7 @@
|
||||
* Utility functions for handling platform-specific commands
|
||||
*/
|
||||
|
||||
import { log } from "./log.js";
|
||||
import { log } from "../logger/log.js";
|
||||
|
||||
/**
|
||||
* Map of Unix commands to their Windows equivalents
|
||||
|
||||
@@ -2,7 +2,7 @@ import type { ExecResult } from "./interface.js";
|
||||
import type { SpawnOptions } from "child_process";
|
||||
|
||||
import { exec } from "./raw-exec.js";
|
||||
import { log } from "../log.js";
|
||||
import { log } from "../../logger/log.js";
|
||||
|
||||
function getCommonRoots() {
|
||||
return [
|
||||
|
||||
@@ -7,7 +7,7 @@ import type {
|
||||
StdioPipe,
|
||||
} from "child_process";
|
||||
|
||||
import { log } from "../log.js";
|
||||
import { log } from "../../logger/log.js";
|
||||
import { adaptCommandForPlatform } from "../platform-commands.js";
|
||||
import { spawn } from "child_process";
|
||||
import * as os from "os";
|
||||
|
||||
Reference in New Issue
Block a user