Files
llmx/codex-cli/tests/config.test.tsx
Anil Karaka 76a979007e fix: increase output limits for truncating collector (#575)
This Pull Request addresses an issue where the output of commands
executed in the raw-exec utility was being truncated due to restrictive
limits on the number of lines and bytes collected. The truncation caused
the message [Output truncated: too many lines or bytes] to appear when
processing large outputs, which could hinder the functionality of the
CLI.

Changes Made

Increased the maximum output limits in the
[createTruncatingCollector](https://github.com/openai/codex/pull/575)
utility:
Bytes: Increased from 10 KB to 100 KB.
Lines: Increased from 256 lines to 1024 lines.
Installed the @types/node package to resolve missing type definitions
for [NodeJS](https://github.com/openai/codex/pull/575) and
[Buffer](https://github.com/openai/codex/pull/575).
Verified and fixed any related errors in the
[createTruncatingCollector](https://github.com/openai/codex/pull/575)
implementation.

Issue Solved: 

This PR ensures that larger outputs can be processed without truncation,
improving the usability of the CLI for commands that generate extensive
output. https://github.com/openai/codex/issues/509

---------

Co-authored-by: Michael Bolin <bolinfest@gmail.com>
2025-05-05 10:26:55 -07:00

364 lines
11 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import type * as fsType from "fs";
import {
loadConfig,
saveConfig,
DEFAULT_SHELL_MAX_BYTES,
DEFAULT_SHELL_MAX_LINES,
} from "../src/utils/config.js";
import { AutoApprovalMode } from "../src/utils/auto-approval-mode.js";
import { tmpdir } from "os";
import { join } from "path";
import { test, expect, beforeEach, afterEach, vi } from "vitest";
import { providers as defaultProviders } from "../src/utils/providers";
// Inmemory FS store
let memfs: Record<string, string> = {};
// Mock out the parts of "fs" that our config module uses:
vi.mock("fs", async () => {
// now `real` is the actual fs module
const real = (await vi.importActual("fs")) as typeof fsType;
return {
...real,
existsSync: (path: string) => memfs[path] !== undefined,
readFileSync: (path: string) => {
if (memfs[path] === undefined) {
throw new Error("ENOENT");
}
return memfs[path];
},
writeFileSync: (path: string, data: string) => {
memfs[path] = data;
},
mkdirSync: () => {
// no-op in inmemory store
},
rmSync: (path: string) => {
// recursively delete any key under this prefix
const prefix = path.endsWith("/") ? path : path + "/";
for (const key of Object.keys(memfs)) {
if (key === path || key.startsWith(prefix)) {
delete memfs[key];
}
}
},
};
});
let testDir: string;
let testConfigPath: string;
let testInstructionsPath: string;
beforeEach(() => {
memfs = {}; // reset inmemory store
testDir = tmpdir(); // use the OS temp dir as our "cwd"
testConfigPath = join(testDir, "config.json");
testInstructionsPath = join(testDir, "instructions.md");
});
afterEach(() => {
memfs = {};
});
test("loads default config if files don't exist", () => {
const config = loadConfig(testConfigPath, testInstructionsPath, {
disableProjectDoc: true,
});
// Keep the test focused on just checking that default model and instructions are loaded
// so we need to make sure we check just these properties
expect(config.model).toBe("o4-mini");
expect(config.instructions).toBe("");
});
test("saves and loads config correctly", () => {
const testConfig = {
model: "test-model",
instructions: "test instructions",
notify: false,
};
saveConfig(testConfig, testConfigPath, testInstructionsPath);
// Our inmemory fs should now contain those keys:
expect(memfs[testConfigPath]).toContain(`"model": "test-model"`);
expect(memfs[testInstructionsPath]).toBe("test instructions");
const loadedConfig = loadConfig(testConfigPath, testInstructionsPath, {
disableProjectDoc: true,
});
// Check just the specified properties that were saved
expect(loadedConfig.model).toBe(testConfig.model);
expect(loadedConfig.instructions).toBe(testConfig.instructions);
});
test("loads user instructions + project doc when codex.md is present", () => {
// 1) seed memfs: a config JSON, an instructions.md, and a codex.md in the cwd
const userInstr = "here are user instructions";
const projectDoc = "# Project Title\n\nSome projectspecific doc";
// first, make config so loadConfig will see storedConfig
memfs[testConfigPath] = JSON.stringify({ model: "mymodel" }, null, 2);
// then user instructions:
memfs[testInstructionsPath] = userInstr;
// and now our fake codex.md in the cwd:
const codexPath = join(testDir, "codex.md");
memfs[codexPath] = projectDoc;
// 2) loadConfig without disabling projectdoc, but with cwd=testDir
const cfg = loadConfig(testConfigPath, testInstructionsPath, {
cwd: testDir,
});
// 3) assert we got both pieces concatenated
expect(cfg.model).toBe("mymodel");
expect(cfg.instructions).toBe(
userInstr + "\n\n--- project-doc ---\n\n" + projectDoc,
);
});
test("loads and saves approvalMode correctly", () => {
// Setup config with approvalMode
memfs[testConfigPath] = JSON.stringify(
{
model: "mymodel",
approvalMode: AutoApprovalMode.AUTO_EDIT,
},
null,
2,
);
memfs[testInstructionsPath] = "test instructions";
// Load config and verify approvalMode
const loadedConfig = loadConfig(testConfigPath, testInstructionsPath, {
disableProjectDoc: true,
});
// Check approvalMode was loaded correctly
expect(loadedConfig.approvalMode).toBe(AutoApprovalMode.AUTO_EDIT);
// Modify approvalMode and save
const updatedConfig = {
...loadedConfig,
approvalMode: AutoApprovalMode.FULL_AUTO,
};
saveConfig(updatedConfig, testConfigPath, testInstructionsPath);
// Verify saved config contains updated approvalMode
expect(memfs[testConfigPath]).toContain(
`"approvalMode": "${AutoApprovalMode.FULL_AUTO}"`,
);
// Load again and verify updated value
const reloadedConfig = loadConfig(testConfigPath, testInstructionsPath, {
disableProjectDoc: true,
});
expect(reloadedConfig.approvalMode).toBe(AutoApprovalMode.FULL_AUTO);
});
test("loads and saves providers correctly", () => {
// Setup custom providers configuration
const customProviders = {
openai: {
name: "Custom OpenAI",
baseURL: "https://custom-api.openai.com/v1",
envKey: "CUSTOM_OPENAI_API_KEY",
},
anthropic: {
name: "Anthropic",
baseURL: "https://api.anthropic.com",
envKey: "ANTHROPIC_API_KEY",
},
};
// Create config with providers
const testConfig = {
model: "test-model",
provider: "anthropic",
providers: customProviders,
instructions: "test instructions",
notify: false,
};
// Save the config
saveConfig(testConfig, testConfigPath, testInstructionsPath);
// Verify saved config contains providers
expect(memfs[testConfigPath]).toContain(`"providers"`);
expect(memfs[testConfigPath]).toContain(`"Custom OpenAI"`);
expect(memfs[testConfigPath]).toContain(`"Anthropic"`);
expect(memfs[testConfigPath]).toContain(`"provider": "anthropic"`);
// Load config and verify providers were loaded correctly
const loadedConfig = loadConfig(testConfigPath, testInstructionsPath, {
disableProjectDoc: true,
});
// Check providers were loaded correctly
expect(loadedConfig.provider).toBe("anthropic");
expect(loadedConfig.providers).toEqual({
...defaultProviders,
...customProviders,
});
// Test merging with built-in providers
// Create a config with only one custom provider
const partialProviders = {
customProvider: {
name: "Custom Provider",
baseURL: "https://custom-api.example.com",
envKey: "CUSTOM_API_KEY",
},
};
const partialConfig = {
model: "test-model",
provider: "customProvider",
providers: partialProviders,
instructions: "test instructions",
notify: false,
};
// Save the partial config
saveConfig(partialConfig, testConfigPath, testInstructionsPath);
// Load config and verify providers were merged with built-in providers
const mergedConfig = loadConfig(testConfigPath, testInstructionsPath, {
disableProjectDoc: true,
});
// Check providers is defined
expect(mergedConfig.providers).toBeDefined();
// Use bracket notation to access properties
if (mergedConfig.providers) {
expect(mergedConfig.providers["customProvider"]).toBeDefined();
expect(mergedConfig.providers["customProvider"]).toEqual(
partialProviders.customProvider,
);
// Built-in providers should still be there (like openai)
expect(mergedConfig.providers["openai"]).toBeDefined();
}
});
test("saves and loads instructions with project doc separator correctly", () => {
const userInstructions = "user specific instructions";
const projectDoc = "project specific documentation";
const combinedInstructions = `${userInstructions}\n\n--- project-doc ---\n\n${projectDoc}`;
const testConfig = {
model: "test-model",
instructions: combinedInstructions,
notify: false,
};
saveConfig(testConfig, testConfigPath, testInstructionsPath);
expect(memfs[testInstructionsPath]).toBe(userInstructions);
const loadedConfig = loadConfig(testConfigPath, testInstructionsPath, {
disableProjectDoc: true,
});
expect(loadedConfig.instructions).toBe(userInstructions);
});
test("handles empty user instructions when saving with project doc separator", () => {
const projectDoc = "project specific documentation";
const combinedInstructions = `\n\n--- project-doc ---\n\n${projectDoc}`;
const testConfig = {
model: "test-model",
instructions: combinedInstructions,
notify: false,
};
saveConfig(testConfig, testConfigPath, testInstructionsPath);
expect(memfs[testInstructionsPath]).toBe("");
const loadedConfig = loadConfig(testConfigPath, testInstructionsPath, {
disableProjectDoc: true,
});
expect(loadedConfig.instructions).toBe("");
});
test("loads default shell config when not specified", () => {
// Setup config without shell settings
memfs[testConfigPath] = JSON.stringify(
{
model: "mymodel",
},
null,
2,
);
memfs[testInstructionsPath] = "test instructions";
// Load config and verify default shell settings
const loadedConfig = loadConfig(testConfigPath, testInstructionsPath, {
disableProjectDoc: true,
});
// Check shell settings were loaded with defaults
expect(loadedConfig.tools).toBeDefined();
expect(loadedConfig.tools?.shell).toBeDefined();
expect(loadedConfig.tools?.shell?.maxBytes).toBe(DEFAULT_SHELL_MAX_BYTES);
expect(loadedConfig.tools?.shell?.maxLines).toBe(DEFAULT_SHELL_MAX_LINES);
});
test("loads and saves custom shell config", () => {
// Setup config with custom shell settings
const customMaxBytes = 12_410;
const customMaxLines = 500;
memfs[testConfigPath] = JSON.stringify(
{
model: "mymodel",
tools: {
shell: {
maxBytes: customMaxBytes,
maxLines: customMaxLines,
},
},
},
null,
2,
);
memfs[testInstructionsPath] = "test instructions";
// Load config and verify custom shell settings
const loadedConfig = loadConfig(testConfigPath, testInstructionsPath, {
disableProjectDoc: true,
});
// Check shell settings were loaded correctly
expect(loadedConfig.tools?.shell?.maxBytes).toBe(customMaxBytes);
expect(loadedConfig.tools?.shell?.maxLines).toBe(customMaxLines);
// Modify shell settings and save
const updatedMaxBytes = 20_000;
const updatedMaxLines = 1_000;
const updatedConfig = {
...loadedConfig,
tools: {
shell: {
maxBytes: updatedMaxBytes,
maxLines: updatedMaxLines,
},
},
};
saveConfig(updatedConfig, testConfigPath, testInstructionsPath);
// Verify saved config contains updated shell settings
expect(memfs[testConfigPath]).toContain(`"maxBytes": ${updatedMaxBytes}`);
expect(memfs[testConfigPath]).toContain(`"maxLines": ${updatedMaxLines}`);
// Load again and verify updated values
const reloadedConfig = loadConfig(testConfigPath, testInstructionsPath, {
disableProjectDoc: true,
});
expect(reloadedConfig.tools?.shell?.maxBytes).toBe(updatedMaxBytes);
expect(reloadedConfig.tools?.shell?.maxLines).toBe(updatedMaxLines);
});