From 39fca5729abd4c8d24de96d2f2973584d0366386 Mon Sep 17 00:00:00 2001 From: Error Date: Fri, 21 Nov 2025 20:27:03 -0600 Subject: [PATCH 1/2] Add configurable env tail append option --- lib/config.ts | 29 ++++++++++++++----------- lib/request/fetch-helpers.ts | 4 +++- lib/request/request-transformer.ts | 18 ++++++++++++---- lib/types.ts | 6 ++++++ spec/append-env-context-config.md | 29 +++++++++++++++++++++++++ test/cache-e2e.test.ts | 11 +++++++--- test/plugin-config.test.ts | 34 ++++++++++++++++++++++++++++++ test/request-transformer.test.ts | 30 ++++++++++++++++++++------ 8 files changed, 135 insertions(+), 26 deletions(-) create mode 100644 spec/append-env-context-config.md diff --git a/lib/config.ts b/lib/config.ts index 677926c..e7ed695 100644 --- a/lib/config.ts +++ b/lib/config.ts @@ -9,14 +9,17 @@ const CONFIG_PATH = getOpenCodePath("openhax-codex-config.json"); * CODEX_MODE is enabled by default for better Codex CLI parity * Prompt caching is enabled by default to optimize token usage and reduce costs */ -const DEFAULT_CONFIG: PluginConfig = { - codexMode: true, - enablePromptCaching: true, - logging: { - showWarningToasts: false, - logWarningsToConsole: false, - }, -}; +function getDefaultConfig(): PluginConfig { + return { + codexMode: true, + enablePromptCaching: true, + appendEnvContext: process.env.CODEX_APPEND_ENV_CONTEXT === "1", + logging: { + showWarningToasts: false, + logWarningsToConsole: false, + }, + }; +} let cachedPluginConfig: PluginConfig | undefined; @@ -38,10 +41,11 @@ export function loadPluginConfig(options: { forceReload?: boolean } = {}): Plugi } try { + const defaults = getDefaultConfig(); const fileContent = safeReadFile(CONFIG_PATH); if (!fileContent) { logWarn("Plugin config file not found, using defaults", { path: CONFIG_PATH }); - cachedPluginConfig = { ...DEFAULT_CONFIG }; + cachedPluginConfig = { ...defaults }; return cachedPluginConfig; } @@ -50,20 +54,21 @@ export function loadPluginConfig(options: { forceReload?: boolean } = {}): Plugi // Merge with defaults (shallow merge + nested logging merge) cachedPluginConfig = { - ...DEFAULT_CONFIG, + ...defaults, ...userConfig, logging: { - ...DEFAULT_CONFIG.logging, + ...defaults.logging, ...userLogging, }, }; return cachedPluginConfig; } catch (error) { + const defaults = getDefaultConfig(); logWarn("Failed to load plugin config", { path: CONFIG_PATH, error: (error as Error).message, }); - cachedPluginConfig = { ...DEFAULT_CONFIG }; + cachedPluginConfig = { ...defaults }; return cachedPluginConfig; } } diff --git a/lib/request/fetch-helpers.ts b/lib/request/fetch-helpers.ts index f904197..41108f4 100644 --- a/lib/request/fetch-helpers.ts +++ b/lib/request/fetch-helpers.ts @@ -124,7 +124,7 @@ export async function transformRequestForCodex( userConfig: UserConfig, codexMode = true, sessionManager?: SessionManager, - _pluginConfig?: PluginConfig, + pluginConfig?: PluginConfig, ): Promise< | { body: RequestBody; @@ -160,7 +160,9 @@ export async function transformRequestForCodex( codexMode, { preserveIds: sessionContext?.preserveIds, + appendEnvContext: pluginConfig?.appendEnvContext ?? process.env.CODEX_APPEND_ENV_CONTEXT === "1", }, + sessionContext, ); const appliedContext = diff --git a/lib/request/request-transformer.ts b/lib/request/request-transformer.ts index f61f6b7..83fef9a 100644 --- a/lib/request/request-transformer.ts +++ b/lib/request/request-transformer.ts @@ -25,6 +25,8 @@ export { getModelConfig, getReasoningConfig, normalizeModel } from "./model-conf export interface TransformRequestOptions { /** Preserve IDs when prompt caching requires it. */ preserveIds?: boolean; + /** Reattach env/files context to prompt tail (defaults from config/env). */ + appendEnvContext?: boolean; } export interface TransformResult { @@ -37,6 +39,7 @@ async function transformInputForCodex( codexMode: boolean, preserveIds: boolean, hasNormalizedTools: boolean, + appendEnvContext: boolean, sessionContext?: SessionContext, ): Promise { if (!body.input || !Array.isArray(body.input)) { @@ -62,8 +65,7 @@ async function transformInputForCodex( } if (codexMode) { - const appendEnvTail = process.env.CODEX_APPEND_ENV_CONTEXT === "1"; - if (appendEnvTail) { + if (appendEnvContext) { const result = await filterOpenCodeSystemPromptsWithEnv(workingInput); workingInput = result?.input; if (result?.envSegments?.length) { @@ -126,8 +128,16 @@ export async function transformRequestBody( logCacheKeyDecision(cacheKeyResult, isNewSession); const hasNormalizedTools = normalizeToolsForCodexBody(body, false); - - await transformInputForCodex(body, codexMode, preserveIds, hasNormalizedTools, sessionContext); + const appendEnvContext = options.appendEnvContext ?? process.env.CODEX_APPEND_ENV_CONTEXT === "1"; + + await transformInputForCodex( + body, + codexMode, + preserveIds, + hasNormalizedTools, + appendEnvContext, + sessionContext, + ); const reasoningConfig = getReasoningConfig(originalModel, modelConfig); body.reasoning = { diff --git a/lib/types.ts b/lib/types.ts index 9567d9c..da74ab4 100644 --- a/lib/types.ts +++ b/lib/types.ts @@ -17,6 +17,12 @@ export interface PluginConfig { */ enablePromptCaching?: boolean; + /** + * Reattach stripped environment/file context to the end of the prompt + * Default inherits from CODEX_APPEND_ENV_CONTEXT env var + */ + appendEnvContext?: boolean; + /** * Logging configuration that can override environment variables */ diff --git a/spec/append-env-context-config.md b/spec/append-env-context-config.md new file mode 100644 index 0000000..c0aebc9 --- /dev/null +++ b/spec/append-env-context-config.md @@ -0,0 +1,29 @@ +# Append Env Context Config + +## Scope + +- Add `appendEnvContext` to plugin config with default derived from `CODEX_APPEND_ENV_CONTEXT`. +- Route env/file tail reattachment through config/options instead of global env in transforms. +- Make tests explicit about env-tail behavior to avoid leaked env state. + +## Touched Files + +- lib/types.ts (PluginConfig additions) +- lib/config.ts (default config from env, merge logic) +- lib/request/request-transformer.ts (options-driven appendEnvContext handling) +- lib/request/fetch-helpers.ts (propagate config to transform) +- test/request-transformer.test.ts (explicit appendEnvContext expectations) +- test/cache-e2e.test.ts (pass plugin config with appendEnvContext=false) +- test/plugin-config.test.ts (cover env default + overrides, reset env) + +## Definition of Done + +- appendEnvContext configurable via config file with default from env. +- Transform respects config-driven appendEnvContext; no hard env dependency in tests. +- Cache/request-transformer tests pass with explicit config; plugin config tests cover new field. +- pnpm test test/cache-e2e.test.ts test/request-transformer.test.ts test/plugin-config.test.ts succeeds. + +## Notes + +- No existing issues/PRs linked for this change. +- Defaults recalc on load; `forceReload` picks up env changes. diff --git a/test/cache-e2e.test.ts b/test/cache-e2e.test.ts index 3c201ef..864efe8 100644 --- a/test/cache-e2e.test.ts +++ b/test/cache-e2e.test.ts @@ -1,8 +1,8 @@ -import { describe, it, expect, vi, afterEach } from "vitest"; +import { describe, it, expect, vi, beforeEach, afterEach } from "vitest"; import { transformRequestForCodex } from "../lib/request/fetch-helpers.js"; import { SessionManager } from "../lib/session/session-manager.js"; import * as openCodeCodex from "../lib/prompts/opencode-codex.js"; -import type { InputItem, RequestBody, UserConfig } from "../lib/types.js"; +import type { InputItem, PluginConfig, RequestBody, UserConfig } from "../lib/types.js"; import * as logger from "../lib/logger.js"; const CODEX_INSTRUCTIONS = "codex instructions"; @@ -30,7 +30,11 @@ function envMessage(date: string, files: string[]): InputItem { }; } -async function runTransform(body: RequestBody, sessionManager: SessionManager) { +async function runTransform( + body: RequestBody, + sessionManager: SessionManager, + pluginConfig: PluginConfig = { appendEnvContext: false }, +) { const init: RequestInit = { body: JSON.stringify(body) }; const result = await transformRequestForCodex( init, @@ -39,6 +43,7 @@ async function runTransform(body: RequestBody, sessionManager: SessionManager) { USER_CONFIG, true, sessionManager, + pluginConfig, ); if (!result) throw new Error("transformRequestForCodex returned undefined"); return result; diff --git a/test/plugin-config.test.ts b/test/plugin-config.test.ts index 513b98e..39cff8a 100644 --- a/test/plugin-config.test.ts +++ b/test/plugin-config.test.ts @@ -29,9 +29,13 @@ beforeEach(async () => { describe("Plugin Configuration", () => { let originalEnv: string | undefined; + let originalAppendEnv: string | undefined; beforeEach(() => { originalEnv = process.env.CODEX_MODE; + originalAppendEnv = process.env.CODEX_APPEND_ENV_CONTEXT; + delete process.env.CODEX_MODE; + delete process.env.CODEX_APPEND_ENV_CONTEXT; vi.clearAllMocks(); }); @@ -41,6 +45,12 @@ describe("Plugin Configuration", () => { } else { process.env.CODEX_MODE = originalEnv; } + + if (originalAppendEnv === undefined) { + delete process.env.CODEX_APPEND_ENV_CONTEXT; + } else { + process.env.CODEX_APPEND_ENV_CONTEXT = originalAppendEnv; + } }); describe("loadPluginConfig", () => { @@ -52,6 +62,7 @@ describe("Plugin Configuration", () => { expect(config).toEqual({ codexMode: true, enablePromptCaching: true, + appendEnvContext: false, logging: { showWarningToasts: false, logWarningsToConsole: false }, }); @@ -69,6 +80,7 @@ describe("Plugin Configuration", () => { expect(config).toEqual({ codexMode: false, enablePromptCaching: true, + appendEnvContext: false, logging: { showWarningToasts: false, logWarningsToConsole: false }, }); }); @@ -82,10 +94,30 @@ describe("Plugin Configuration", () => { expect(config).toEqual({ codexMode: true, enablePromptCaching: true, + appendEnvContext: false, logging: { showWarningToasts: false, logWarningsToConsole: false }, }); }); + it("should default appendEnvContext from env when config missing", () => { + process.env.CODEX_APPEND_ENV_CONTEXT = "1"; + mockExistsSync.mockReturnValue(false); + + const config = loadPluginConfig({ forceReload: true }); + + expect(config.appendEnvContext).toBe(true); + }); + + it("should let config override appendEnvContext even when env is set", () => { + process.env.CODEX_APPEND_ENV_CONTEXT = "1"; + mockExistsSync.mockReturnValue(true); + mockReadFileSync.mockReturnValue(JSON.stringify({ appendEnvContext: false })); + + const config = loadPluginConfig({ forceReload: true }); + + expect(config.appendEnvContext).toBe(false); + }); + it("should merge nested logging config with defaults", () => { mockExistsSync.mockReturnValue(true); mockReadFileSync.mockReturnValue( @@ -112,6 +144,7 @@ describe("Plugin Configuration", () => { expect(config).toEqual({ codexMode: true, enablePromptCaching: true, + appendEnvContext: false, logging: { showWarningToasts: false, logWarningsToConsole: false }, }); @@ -131,6 +164,7 @@ describe("Plugin Configuration", () => { expect(config).toEqual({ codexMode: true, enablePromptCaching: true, + appendEnvContext: false, logging: { showWarningToasts: false, logWarningsToConsole: false }, }); expect(logWarnSpy).toHaveBeenCalled(); diff --git a/test/request-transformer.test.ts b/test/request-transformer.test.ts index 9b9ab6e..9198335 100644 --- a/test/request-transformer.test.ts +++ b/test/request-transformer.test.ts @@ -714,7 +714,14 @@ describe("transformRequestBody caching stability", () => { ], }; - const result1 = await transformRequestBody(body1, CODEX_INSTRUCTIONS, userConfig, true, {}, undefined); + const result1 = await transformRequestBody( + body1, + CODEX_INSTRUCTIONS, + userConfig, + true, + { appendEnvContext: false }, + undefined, + ); expect(result1.prompt_cache_key).toContain("conv-env-stable"); expect(result1.input).toHaveLength(1); expect(result1.input?.[0].role).toBe("user"); @@ -728,14 +735,20 @@ describe("transformRequestBody caching stability", () => { ], }; - const result2 = await transformRequestBody(body2, CODEX_INSTRUCTIONS, userConfig, true, {}, undefined); + const result2 = await transformRequestBody( + body2, + CODEX_INSTRUCTIONS, + userConfig, + true, + { appendEnvContext: false }, + undefined, + ); expect(result2.prompt_cache_key).toBe(result1.prompt_cache_key); expect(result2.input).toHaveLength(1); expect(result2.input?.[0].role).toBe("user"); }); it("can reattach env/files tail when flag enabled", async () => { - process.env.CODEX_APPEND_ENV_CONTEXT = "1"; const body: RequestBody = { model: "gpt-5", metadata: { conversation_id: "conv-env-tail" }, @@ -762,14 +775,19 @@ describe("transformRequestBody caching stability", () => { ], }; - const result = await transformRequestBody(body, CODEX_INSTRUCTIONS, userConfig, true, {}, undefined); + const result = await transformRequestBody( + body, + CODEX_INSTRUCTIONS, + userConfig, + true, + { appendEnvContext: true }, + undefined, + ); expect(result.input?.length).toBe(2); expect(result.input?.[0].role).toBe("user"); expect(result.input?.[1].role).toBe("developer"); expect(result.input?.[1].content as string).toContain(""); expect(result.input?.[1].content as string).toContain(""); - - delete process.env.CODEX_APPEND_ENV_CONTEXT; }); }); From 588d2260402b03c9c1f865e5d8470cfec32bdd5d Mon Sep 17 00:00:00 2001 From: Err Date: Fri, 21 Nov 2025 20:34:25 -0600 Subject: [PATCH 2/2] Potential fix for pull request finding 'Unused variable, import, function or class' Co-authored-by: Copilot Autofix powered by AI <223894421+github-code-quality[bot]@users.noreply.github.com> --- test/cache-e2e.test.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/cache-e2e.test.ts b/test/cache-e2e.test.ts index 864efe8..4fea7fd 100644 --- a/test/cache-e2e.test.ts +++ b/test/cache-e2e.test.ts @@ -1,4 +1,4 @@ -import { describe, it, expect, vi, beforeEach, afterEach } from "vitest"; +import { describe, it, expect, vi, afterEach } from "vitest"; import { transformRequestForCodex } from "../lib/request/fetch-helpers.js"; import { SessionManager } from "../lib/session/session-manager.js"; import * as openCodeCodex from "../lib/prompts/opencode-codex.js";