Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -64,7 +64,8 @@ import type { UserConfig } from "./lib/types.js";
* ```
*/
export const OpenAIAuthPlugin: Plugin = async ({ client, directory }: PluginInput) => {
configureLogger({ client, directory });
const pluginConfig = loadPluginConfig();
configureLogger({ client, directory, pluginConfig });
setTimeout(() => {
logWarn(
"The OpenAI Codex plugin is intended for personal use with your own ChatGPT Plus/Pro subscription. Ensure your usage complies with OpenAI's Terms of Service.",
Expand Down Expand Up @@ -98,7 +99,6 @@ export const OpenAIAuthPlugin: Plugin = async ({ client, directory }: PluginInpu
};

// Load plugin configuration and determine CODEX_MODE
const pluginConfig = loadPluginConfig();
const codexMode = getCodexMode(pluginConfig);
const promptCachingEnabled = pluginConfig.enablePromptCaching ?? true;
if (!promptCachingEnabled) {
Expand Down
14 changes: 11 additions & 3 deletions lib/config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,9 @@ const DEFAULT_CONFIG: PluginConfig = {
enablePromptCaching: true,
enableCodexCompaction: true,
autoCompactMinMessages: 8,
logging: {
showWarningToasts: false,
},
};

let cachedPluginConfig: PluginConfig | undefined;
Expand All @@ -39,24 +42,29 @@ export function loadPluginConfig(options: { forceReload?: boolean } = {}): Plugi
const fileContent = safeReadFile(CONFIG_PATH);
if (!fileContent) {
logWarn("Plugin config file not found, using defaults", { path: CONFIG_PATH });
cachedPluginConfig = DEFAULT_CONFIG;
cachedPluginConfig = { ...DEFAULT_CONFIG };
return cachedPluginConfig;
}

const userConfig = JSON.parse(fileContent) as Partial<PluginConfig>;
const userLogging = userConfig.logging ?? {};

// Merge with defaults
// Merge with defaults (shallow merge + nested logging merge)
cachedPluginConfig = {
...DEFAULT_CONFIG,
...userConfig,
logging: {
...DEFAULT_CONFIG.logging,
...userLogging,
},
};
return cachedPluginConfig;
} catch (error) {
logWarn("Failed to load plugin config", {
path: CONFIG_PATH,
error: (error as Error).message,
});
cachedPluginConfig = DEFAULT_CONFIG;
cachedPluginConfig = { ...DEFAULT_CONFIG };
return cachedPluginConfig;
}
}
Expand Down
66 changes: 56 additions & 10 deletions lib/logger.ts
Original file line number Diff line number Diff line change
@@ -1,26 +1,41 @@
import type { OpencodeClient } from "@opencode-ai/sdk";
import { appendFile, rename, rm, stat, writeFile } from "node:fs/promises";
import { join } from "node:path";
import type { LoggingConfig, PluginConfig } from "./types.js";
Comment thread
riatzukiza marked this conversation as resolved.
import { PLUGIN_NAME } from "./constants.js";
import { ensureDirectory, getOpenCodePath } from "./utils/file-system-utils.js";

export const LOGGING_ENABLED = process.env.ENABLE_PLUGIN_REQUEST_LOGGING === "1";
const DEBUG_FLAG_ENABLED = process.env.DEBUG_CODEX_PLUGIN === "1";
const DEBUG_ENABLED = DEBUG_FLAG_ENABLED || LOGGING_ENABLED;
const IS_TEST_ENV = process.env.NODE_ENV === "test";
const CONSOLE_LOGGING_ENABLED = DEBUG_FLAG_ENABLED && !IS_TEST_ENV;
const LOG_DIR = getOpenCodePath("logs", "codex-plugin");
const ROLLING_LOG_FILE = join(LOG_DIR, "codex-plugin.log");

const LOG_ROTATION_MAX_BYTES = Math.max(1, getEnvNumber("CODEX_LOG_MAX_BYTES", 5 * 1024 * 1024));
const LOG_ROTATION_MAX_FILES = Math.max(1, getEnvNumber("CODEX_LOG_MAX_FILES", 5));
const LOG_QUEUE_MAX_LENGTH = Math.max(1, getEnvNumber("CODEX_LOG_QUEUE_MAX", 1000));
const envLoggingDefaults = {
loggingEnabled: process.env.ENABLE_PLUGIN_REQUEST_LOGGING === "1",
debugFlagEnabled: process.env.DEBUG_CODEX_PLUGIN === "1",
showWarningToasts: process.env.CODEX_SHOW_WARNING_TOASTS === "1",
logRotationMaxBytes: getEnvNumber("CODEX_LOG_MAX_BYTES", 5 * 1024 * 1024),
logRotationMaxFiles: getEnvNumber("CODEX_LOG_MAX_FILES", 5),
logQueueMaxLength: getEnvNumber("CODEX_LOG_QUEUE_MAX", 1000),
};

let LOGGING_ENABLED = envLoggingDefaults.loggingEnabled;
export function isLoggingEnabled(): boolean {
return LOGGING_ENABLED;
}
let DEBUG_FLAG_ENABLED = envLoggingDefaults.debugFlagEnabled;
let WARN_TOASTS_ENABLED = envLoggingDefaults.showWarningToasts ?? false;
Comment thread
riatzukiza marked this conversation as resolved.
let LOG_ROTATION_MAX_BYTES = Math.max(1, envLoggingDefaults.logRotationMaxBytes);
let LOG_ROTATION_MAX_FILES = Math.max(1, envLoggingDefaults.logRotationMaxFiles);
let LOG_QUEUE_MAX_LENGTH = Math.max(1, envLoggingDefaults.logQueueMaxLength);
let DEBUG_ENABLED = DEBUG_FLAG_ENABLED || LOGGING_ENABLED;
let CONSOLE_LOGGING_ENABLED = DEBUG_FLAG_ENABLED && !IS_TEST_ENV;

type LogLevel = "debug" | "info" | "warn" | "error";

type LoggerOptions = {
client?: OpencodeClient;
directory?: string;
pluginConfig?: PluginConfig;
};

type OpencodeClientWithTui = OpencodeClient & {
Expand All @@ -46,6 +61,35 @@ type RollingLogEntry = {
extra?: Record<string, unknown>;
};

function refreshLoggingState(): void {
DEBUG_ENABLED = DEBUG_FLAG_ENABLED || LOGGING_ENABLED;
CONSOLE_LOGGING_ENABLED = DEBUG_FLAG_ENABLED && !IS_TEST_ENV;
}

function ensurePositiveNumber(value: number | undefined, fallback: number): number {
if (typeof value === "number" && Number.isFinite(value) && value > 0) {
return value;
}
return fallback;
}

function applyLoggingOverrides(logging?: LoggingConfig): void {
if (!logging) {
refreshLoggingState();
return;
}

LOGGING_ENABLED = logging.enableRequestLogging ?? LOGGING_ENABLED;
DEBUG_FLAG_ENABLED = logging.debug ?? DEBUG_FLAG_ENABLED;
WARN_TOASTS_ENABLED = logging.showWarningToasts ?? WARN_TOASTS_ENABLED;
LOG_ROTATION_MAX_BYTES = ensurePositiveNumber(logging.logMaxBytes, LOG_ROTATION_MAX_BYTES);
LOG_ROTATION_MAX_FILES = ensurePositiveNumber(logging.logMaxFiles, LOG_ROTATION_MAX_FILES);
LOG_QUEUE_MAX_LENGTH = ensurePositiveNumber(logging.logQueueMax, LOG_QUEUE_MAX_LENGTH);
refreshLoggingState();
}

refreshLoggingState();

let requestCounter = 0;
let loggerClient: OpencodeClient | undefined;
let projectDirectory: string | undefined;
Expand All @@ -66,6 +110,7 @@ export function configureLogger(options: LoggerOptions = {}): void {
if (options.directory) {
projectDirectory = options.directory;
}
applyLoggingOverrides(options.pluginConfig?.logging);
if (announcedState || !(LOGGING_ENABLED || DEBUG_ENABLED)) {
return;
}
Expand Down Expand Up @@ -127,6 +172,7 @@ export async function flushRollingLogsForTest(): Promise<void> {
function emit(level: LogLevel, message: string, extra?: Record<string, unknown>): void {
const sanitizedExtra = sanitizeExtra(extra);
const supportsToast = loggerClient ? hasTuiShowToast(loggerClient) : false;
const warnToastEnabled = supportsToast && WARN_TOASTS_ENABLED;
const entry: RollingLogEntry = {
timestamp: new Date().toISOString(),
service: PLUGIN_NAME,
Expand All @@ -139,7 +185,7 @@ function emit(level: LogLevel, message: string, extra?: Record<string, unknown>)
appendRollingLog(entry);
}

const shouldForwardToAppLog = level !== "warn" || !supportsToast;
const shouldForwardToAppLog = level !== "warn" || !warnToastEnabled;

if (shouldForwardToAppLog && loggerClient?.app?.log) {
void loggerClient.app
Expand All @@ -154,11 +200,11 @@ function emit(level: LogLevel, message: string, extra?: Record<string, unknown>)
);
}

if (level === "error" || (level === "warn" && supportsToast)) {
if (level === "error" || (level === "warn" && warnToastEnabled)) {
notifyToast(level, message, sanitizedExtra);
}

const shouldLogToConsole = level !== "warn" || !supportsToast;
const shouldLogToConsole = level !== "warn" || !warnToastEnabled;
if (shouldLogToConsole) {
logToConsole(level, message, sanitizedExtra);
}
Expand Down
4 changes: 2 additions & 2 deletions lib/request/response-handler.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { PLUGIN_NAME } from "../constants.js";
import { LOGGING_ENABLED, logError, logRequest } from "../logger.js";
import { isLoggingEnabled, logError, logRequest } from "../logger.js";
import type { SSEEventData } from "../types.js";

/**
Expand Down Expand Up @@ -50,7 +50,7 @@ export async function convertSseToJson(response: Response, headers: Headers): Pr
fullText += decoder.decode(value, { stream: true });
}

if (LOGGING_ENABLED) {
if (isLoggingEnabled()) {
logRequest("stream-full", { fullContent: fullText });
}

Expand Down
20 changes: 20 additions & 0 deletions lib/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,26 @@ export interface PluginConfig {
* Minimum number of conversation messages before auto-compacting
*/
autoCompactMinMessages?: number;

/**
* Logging configuration that can override environment variables
*/
logging?: LoggingConfig;
}

export interface LoggingConfig {
/** When true, persist detailed request logs regardless of env var */
enableRequestLogging?: boolean;
/** When true, enable debug logging regardless of env var */
debug?: boolean;
/** Whether warning-level toasts should be shown (default: false) */
showWarningToasts?: boolean;
/** Override max bytes before rolling log rotation */
logMaxBytes?: number;
/** Override number of rotated log files to keep */
logMaxFiles?: number;
/** Override rolling log queue length */
logQueueMax?: number;
}

/**
Expand Down
4 changes: 2 additions & 2 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

43 changes: 43 additions & 0 deletions spec/environment-variables.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
# Environment Variables Audit

## Context

- User requested a list/summary of all environment variables used by this repository.

## Sources (code refs)

- lib/logger.ts:12-27,428-435 (logging flags, rotation tuning via env, generic accessor)
- lib/config.ts:72-76 (CODEX_MODE override)
- scripts/sync-github-secrets.mjs:5-10,63-65,106-114 (default secret names, repo detection, env lookup)
- scripts/review-response-context.mjs:7-10,104-110 (GitHub Actions paths/outputs)
- scripts/detect-release-type.mjs:18-21,30-31 (release base ref/head sha overrides)

## Environment variables and purposes

- `ENABLE_PLUGIN_REQUEST_LOGGING` (`lib/logger.ts:7`): when "1", persist detailed request logs.
- `DEBUG_CODEX_PLUGIN` (`lib/logger.ts:8`): when "1", enable debug logging/console output (unless NODE_ENV=test).
- `NODE_ENV` (`lib/logger.ts:10`): if "test", suppress console logging during tests.
- `CODEX_LOG_MAX_BYTES` (`lib/logger.ts:16`): max rolling log file size before rotation (default 5MB).
- `CODEX_LOG_MAX_FILES` (`lib/logger.ts:17`): how many rotated log files to keep (default 5).
- `CODEX_LOG_QUEUE_MAX` (`lib/logger.ts:18`): max queued log entries before overflow warning (default 1000).
- `CODEX_SHOW_WARNING_TOASTS` (`lib/logger.ts:15`): when "1", allow warning-level toasts (config default keeps them off).
- `CODEX_MODE` (`lib/config.ts:72-76`): if set, overrides config; "1" enables Codex bridge/tool mapping, otherwise disables.
- `GITHUB_REPOSITORY` (`scripts/sync-github-secrets.mjs:63-65`): optional repo inference fallback for syncing secrets.
- `NPM_TOKEN`, `OPENCODE_API_KEY`, `OPENCODE_API_URL`, `RELEASE_BASE_REF` (`scripts/sync-github-secrets.mjs:5-10`): default env names expected when syncing secrets (first two required, latter two optional unless explicitly requested).
- `GITHUB_EVENT_PATH` (`scripts/review-response-context.mjs:7-10`): required path to event payload in review-comment workflow.
- `GITHUB_OUTPUT` (`scripts/review-response-context.mjs:104-110`): optional path to append action outputs.
- `RELEASE_BASE_REF` (`scripts/detect-release-type.mjs:18-21`): optional override for release comparison base.
- `GITHUB_SHA` (`scripts/detect-release-type.mjs:30-31`): optional head sha override (falls back to git rev-parse HEAD).

## Existing issues/PRs

- None identified during this audit.

## Definition of done

- Enumerate all environment variables referenced in code/scripts with locations and purposes; provide user-facing summary.

## Notes

- Secret sync script can read any env name specified via CLI args in addition to defaults; above list reflects defaults plus repo inference variables.
- Logging-related env vars can be overridden in ~/.opencode/openhax-codex-config.json via the `logging` block.
2 changes: 1 addition & 1 deletion test/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -79,7 +79,7 @@ Tests SSE to JSON conversion:

Tests logging functionality:

- LOGGING_ENABLED constant
- isLoggingEnabled() function
- logRequest function parameter handling
- Complex data structure support

Expand Down
2 changes: 1 addition & 1 deletion test/cache-warming.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ vi.mock("../lib/logger.js", () => ({
logDebug: vi.fn(),
logWarn: vi.fn(),
logRequest: vi.fn(),
LOGGING_ENABLED: false,
isLoggingEnabled: () => false,
}));

const mockGetCodexInstructions = getCodexInstructions as ReturnType<typeof vi.fn>;
Expand Down
54 changes: 49 additions & 5 deletions test/logger.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -65,10 +65,14 @@ afterEach(() => {
});

describe("logger", () => {
it("LOGGING_ENABLED reflects env state", async () => {
it("isLoggingEnabled reflects env state", async () => {
process.env.ENABLE_PLUGIN_REQUEST_LOGGING = "1";
const { LOGGING_ENABLED } = await import("../lib/logger.js");
expect(LOGGING_ENABLED).toBe(true);
const { isLoggingEnabled, configureLogger } = await import("../lib/logger.js");
expect(isLoggingEnabled()).toBe(true);

// Test that config overrides are reflected
configureLogger({ pluginConfig: { logging: { enableRequestLogging: false } } });
expect(isLoggingEnabled()).toBe(false);
});

it("logRequest writes stage file and rolling log when enabled", async () => {
Expand Down Expand Up @@ -107,6 +111,25 @@ describe("logger", () => {
expect(fsMocks.appendFile).not.toHaveBeenCalled();
});

it("config overrides env-enabled request logging when disabled in file", async () => {
process.env.ENABLE_PLUGIN_REQUEST_LOGGING = "1";
fsMocks.existsSync.mockReturnValue(true);
const { configureLogger, logRequest, flushRollingLogsForTest, isLoggingEnabled } = await import(
"../lib/logger.js"
);

configureLogger({ pluginConfig: { logging: { enableRequestLogging: false } } });

// Verify isLoggingEnabled reflects the config override
expect(isLoggingEnabled()).toBe(false);

logRequest("stage-one", { foo: "bar" });
await flushRollingLogsForTest();

expect(fsMocks.writeFile).not.toHaveBeenCalled();
expect(fsMocks.appendFile).not.toHaveBeenCalled();
});

it("logDebug appends to rolling log only when enabled", async () => {
process.env.ENABLE_PLUGIN_REQUEST_LOGGING = "1";
fsMocks.existsSync.mockReturnValue(true);
Expand All @@ -129,7 +152,7 @@ describe("logger", () => {
expect(warnSpy).toHaveBeenCalledWith("[openhax/codex] warning");
});

it("logWarn sends toast and avoids console/app log when tui available", async () => {
it("logWarn does not send warning toasts by default even when tui is available", async () => {
fsMocks.existsSync.mockReturnValue(true);
const showToast = vi.fn();
const appLog = vi.fn().mockResolvedValue(undefined);
Expand All @@ -145,6 +168,27 @@ describe("logger", () => {
logWarn("toast-warning");
await flushRollingLogsForTest();

expect(showToast).not.toHaveBeenCalled();
expect(appLog).toHaveBeenCalledTimes(1);
expect(warnSpy).toHaveBeenCalledWith("[openhax/codex] toast-warning");
});

it("logWarn sends warning toasts only when enabled via config", async () => {
fsMocks.existsSync.mockReturnValue(true);
const showToast = vi.fn();
const appLog = vi.fn().mockResolvedValue(undefined);
const { configureLogger, logWarn, flushRollingLogsForTest } = await import("../lib/logger.js");

const client = {
app: { log: appLog },
tui: { showToast },
} as unknown as OpencodeClient;

configureLogger({ client, pluginConfig: { logging: { showWarningToasts: true } } });

logWarn("toast-warning");
await flushRollingLogsForTest();

expect(showToast).toHaveBeenCalledWith({
body: {
title: "openhax/codex warning",
Expand All @@ -167,7 +211,7 @@ describe("logger", () => {
tui: { showToast },
} as unknown as OpencodeClient;

configureLogger({ client });
configureLogger({ client, pluginConfig: { logging: { showWarningToasts: true } } });

logWarn(
"prefix mismatch detected while warming the session cache; reconnecting with fallback account boundaries",
Expand Down
Loading
Loading