Skip to content
Draft
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
119 changes: 119 additions & 0 deletions CodexSharpSDK.Tests/Unit/CodexFeaturesTests.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,119 @@
using System.Reflection;
using System.Text.Json;
using ManagedCode.CodexSharpSDK.Models;

namespace ManagedCode.CodexSharpSDK.Tests.Unit;

public class CodexFeaturesTests
{
private const string SolutionFileName = "ManagedCode.CodexSharpSDK.slnx";
private const string BundledConfigSchemaFileName = "config.schema.json";

[Test]
public async Task CodexFeatures_NewUpstreamFlags_ArePresent()
{
// These two flags were added in upstream commit 3b5fe5c and are the primary
// motivation for this sync. Verify the constants are present and map to the correct
// upstream key strings by resolving them via reflection (avoiding a constant-vs-constant
// comparison that the analyzer rightly flags as a no-op assertion).
var sdkValues = GetSdkFeatureValues();
await Assert.That(sdkValues).Contains("guardian_approval");
await Assert.That(sdkValues).Contains("tool_call_mcp_elicitation");
}

[Test]
public async Task CodexFeatures_AllConstantsAreValidUpstreamFeatureKeys()
{
var schemaFeatureKeys = await ReadBundledSchemaFeatureKeysAsync();
var sdkFeatureValues = GetSdkFeatureValues();
var invalidKeys = sdkFeatureValues
.Except(schemaFeatureKeys, StringComparer.Ordinal)
.ToArray();

await Assert.That(invalidKeys).IsEmpty();
}

[Test]
public async Task CodexFeatures_CoversAllCanonicalUpstreamFeatureKeys()
{
// The canonical (non-alias) keys from features.rs must all have an SDK constant so
// that callers can reference them without magic strings.
var schemaFeatureKeys = await ReadBundledSchemaFeatureKeysAsync();
var sdkFeatureValues = GetSdkFeatureValues();

// Legacy alias keys that exist in config.schema.json but are NOT canonical feature
// keys in features.rs; they are intentionally excluded from CodexFeatures.
var knownAliases = new HashSet<string>(StringComparer.Ordinal)
{
"collab",
"connectors",
"enable_experimental_windows_sandbox",
"experimental_use_freeform_apply_patch",
"experimental_use_unified_exec_tool",
"include_apply_patch_tool",
"memory_tool",
"web_search",
};

var canonicalKeys = schemaFeatureKeys
.Except(knownAliases, StringComparer.Ordinal)
.ToArray();

var missingKeys = canonicalKeys
.Except(sdkFeatureValues, StringComparer.Ordinal)
.ToArray();

await Assert.That(missingKeys).IsEmpty();
}

private static string[] GetSdkFeatureValues()
{
return typeof(CodexFeatures)
.GetFields(BindingFlags.Public | BindingFlags.Static | BindingFlags.DeclaredOnly)
.Where(field => field is { IsLiteral: true, IsInitOnly: false } && field.FieldType == typeof(string))
.Select(field => (string)field.GetRawConstantValue()!)
.ToArray();
}

private static async Task<string[]> ReadBundledSchemaFeatureKeysAsync()
{
var schemaPath = ResolveBundledConfigSchemaFilePath();
using var stream = File.OpenRead(schemaPath);
using var document = await JsonDocument.ParseAsync(stream);

return document.RootElement
.GetProperty("properties")
.GetProperty("features")
.GetProperty("properties")
.EnumerateObject()
.Select(p => p.Name)
.ToArray();
}

private static string ResolveBundledConfigSchemaFilePath()
{
return Path.Combine(
ResolveRepositoryRootPath(),
"submodules",
"openai-codex",
"codex-rs",
"core",
BundledConfigSchemaFileName);
}

private static string ResolveRepositoryRootPath()
{
var current = new DirectoryInfo(AppContext.BaseDirectory);
while (current is not null)
{
if (File.Exists(Path.Combine(current.FullName, SolutionFileName)))
{
return current.FullName;
}

current = current.Parent;
}

throw new InvalidOperationException("Could not locate repository root from test execution directory.");
}
}
71 changes: 71 additions & 0 deletions CodexSharpSDK/Models/CodexFeatures.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
namespace ManagedCode.CodexSharpSDK.Models;

/// <summary>
/// Canonical feature flag keys for use with <see cref="Client.ThreadOptions.EnabledFeatures"/>
/// and <see cref="Client.ThreadOptions.DisabledFeatures"/> (and their equivalents in
/// <see cref="Execution.CodexExecArgs"/>). Values match the keys accepted by the Codex CLI
/// <c>--enable</c> / <c>--disable</c> flags and the <c>[features]</c> section of
/// <c>config.toml</c>. Sourced from the bundled upstream
/// <c>codex-rs/core/src/features.rs</c>.
/// </summary>
public static class CodexFeatures
{
public const string ApplyPatchFreeform = "apply_patch_freeform";
public const string Apps = "apps";
public const string AppsMcpGateway = "apps_mcp_gateway";
public const string Artifact = "artifact";
public const string ChildAgentsMd = "child_agents_md";
public const string CodexGitCommit = "codex_git_commit";
public const string CollaborationModes = "collaboration_modes";
public const string DefaultModeRequestUserInput = "default_mode_request_user_input";
public const string ElevatedWindowsSandbox = "elevated_windows_sandbox";
public const string EnableRequestCompression = "enable_request_compression";
public const string ExperimentalWindowsSandbox = "experimental_windows_sandbox";
public const string FastMode = "fast_mode";

/// <summary>
/// Guardian subagent approval: lets a guardian subagent review <c>on-request</c> approval
/// prompts instead of surfacing them to the user, including sandbox escapes and blocked
/// network access. Experimental feature added in upstream commit 3b5fe5c.
/// </summary>
public const string GuardianApproval = "guardian_approval";

public const string ImageDetailOriginal = "image_detail_original";
public const string ImageGeneration = "image_generation";
public const string JsRepl = "js_repl";
public const string JsReplToolsOnly = "js_repl_tools_only";
public const string Memories = "memories";
public const string MultiAgent = "multi_agent";
public const string Personality = "personality";
public const string Plugins = "plugins";
public const string PowershellUtf8 = "powershell_utf8";
public const string PreventIdleSleep = "prevent_idle_sleep";
public const string RealtimeConversation = "realtime_conversation";
public const string RemoteModels = "remote_models";
public const string RequestPermissions = "request_permissions";
public const string RequestRule = "request_rule";
public const string ResponsesWebsockets = "responses_websockets";
public const string ResponsesWebsocketsV2 = "responses_websockets_v2";
public const string RuntimeMetrics = "runtime_metrics";
public const string SearchTool = "search_tool";
public const string ShellSnapshot = "shell_snapshot";
public const string ShellTool = "shell_tool";
public const string ShellZshFork = "shell_zsh_fork";
public const string SkillEnvVarDependencyPrompt = "skill_env_var_dependency_prompt";
public const string SkillMcpDependencyInstall = "skill_mcp_dependency_install";
public const string Sqlite = "sqlite";
public const string Steer = "steer";

/// <summary>
/// Routes MCP tool approval prompts through the MCP elicitation request path.
/// Under-development feature added in upstream commit 3b5fe5c.
/// </summary>
public const string ToolCallMcpElicitation = "tool_call_mcp_elicitation";

public const string Undo = "undo";
public const string UnifiedExec = "unified_exec";
public const string UseLinuxSandboxBwrap = "use_linux_sandbox_bwrap";
public const string VoiceTranscription = "voice_transcription";
public const string WebSearchCached = "web_search_cached";
public const string WebSearchRequest = "web_search_request";
}
2 changes: 1 addition & 1 deletion global.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"sdk": {
"version": "10.0.103",
"version": "10.0.102",
"rollForward": "latestFeature"
},
"test": {
Expand Down
2 changes: 1 addition & 1 deletion submodules/openai-codex
Submodule openai-codex updated 323 files