From 9527d6b542ef299a1793233985269106a960a9a4 Mon Sep 17 00:00:00 2001 From: fayzan Date: Wed, 12 Aug 2026 09:49:15 +0100 Subject: [PATCH] Handle missing config files codex and claude --- internal/harnesses/claude.go | 2 +- internal/harnesses/claude_test.go | 24 ++++++++++++++++++++++++ internal/harnesses/codex.go | 4 ++-- internal/harnesses/codex_test.go | 31 +++++++++++++++++++++++++++++++ internal/harnesses/merge.go | 16 +++++++++++++++- 5 files changed, 73 insertions(+), 4 deletions(-) diff --git a/internal/harnesses/claude.go b/internal/harnesses/claude.go index c280446..2235f83 100644 --- a/internal/harnesses/claude.go +++ b/internal/harnesses/claude.go @@ -101,7 +101,7 @@ func (c *ClaudeHarness) Configure(opts ConfigureOptions) error { func (c *ClaudeHarness) configureMerge(opts ConfigureOptions) error { settingsPath := c.settingsPath() - settings, err := mergeJSONConfigFile(settingsPath, map[string]any{ + settings, err := mergeOrCreateJSONConfigFile(settingsPath, map[string]any{ "env": map[string]any{ "ANTHROPIC_BASE_URL": c.config.RouterBaseURL, "ANTHROPIC_AUTH_TOKEN": c.config.APIKey, diff --git a/internal/harnesses/claude_test.go b/internal/harnesses/claude_test.go index 7ba86d0..75b3aa3 100644 --- a/internal/harnesses/claude_test.go +++ b/internal/harnesses/claude_test.go @@ -40,6 +40,30 @@ func TestClaudeHarnessRoundTrip(t *testing.T) { assert.Equal(t, true, status.Configured) } +func TestClaudeHarnessConfigureCreatesMissingConfig(t *testing.T) { + config := config.Config{ + RouterBaseURL: "https://router.requesty.ai", + APIKey: "my-api-key", + } + configDir := t.TempDir() + settingsPath := filepath.Join(configDir, "settings.json") + harness := NewClaudeHarness(config, configDir) + + require.NoError(t, harness.Configure(ConfigureOptions{ + Model: "anthropic/claude-fable-5", + })) + + settings, err := os.ReadFile(settingsPath) + require.NoError(t, err) + assert.JSONEq(t, `{ + "env": { + "ANTHROPIC_BASE_URL": "https://router.requesty.ai", + "ANTHROPIC_AUTH_TOKEN": "my-api-key", + "ANTHROPIC_MODEL": "anthropic/claude-fable-5" + } + }`, string(settings)) +} + func TestClaudeHarnessDefaultConfigDir(t *testing.T) { homePath, err := os.UserHomeDir() require.NoError(t, err) diff --git a/internal/harnesses/codex.go b/internal/harnesses/codex.go index 10ad077..40b652c 100644 --- a/internal/harnesses/codex.go +++ b/internal/harnesses/codex.go @@ -125,7 +125,7 @@ func (c *CodexHarness) Configure(opts ConfigureOptions) error { func (c *CodexHarness) configureMerge(opts ConfigureOptions) error { configPath := c.configPath() - config, err := mergeTOMLConfigFile(configPath, map[string]any{ + config, err := mergeOrCreateTOMLConfigFile(configPath, map[string]any{ "model": opts.Model, "model_provider": codexModelProvider, "model_reasoning_effort": "high", @@ -148,7 +148,7 @@ func (c *CodexHarness) configureMerge(opts ConfigureOptions) error { authPath := c.authPath() - auth, err := mergeJSONConfigFile(authPath, map[string]any{ + auth, err := mergeOrCreateJSONConfigFile(authPath, map[string]any{ "auth_mode": "apikey", "OPENAI_API_KEY": c.config.APIKey, }) diff --git a/internal/harnesses/codex_test.go b/internal/harnesses/codex_test.go index 920e051..00e66b3 100644 --- a/internal/harnesses/codex_test.go +++ b/internal/harnesses/codex_test.go @@ -5,6 +5,7 @@ import ( "path/filepath" "testing" + "github.com/pelletier/go-toml/v2" "github.com/requestyai/cli/internal/config" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" @@ -43,6 +44,36 @@ func TestCodexIntegrationRoundTrip(t *testing.T) { assert.Equal(t, true, status.Configured) } +func TestCodexHarnessConfigureCreatesMissingConfig(t *testing.T) { + config := config.Config{ + RouterBaseURL: "https://router.requesty.ai", + APIKey: "my-api-key", + } + configDir := t.TempDir() + configPath := filepath.Join(configDir, "config.toml") + authPath := filepath.Join(configDir, "auth.json") + harness := NewCodexHarness(config, configDir) + + require.NoError(t, harness.Configure(ConfigureOptions{ + Model: "openai-responses/gpt-5.5", + })) + + configBytes, err := os.ReadFile(configPath) + require.NoError(t, err) + var parsedConfig codexConfig + require.NoError(t, toml.Unmarshal(configBytes, &parsedConfig)) + assert.Equal(t, "openai-responses/gpt-5.5", parsedConfig.Model) + assert.Equal(t, codexModelProvider, parsedConfig.ModelProvider) + assert.Equal(t, "https://router.requesty.ai/v1", parsedConfig.ModelProviders[codexModelProvider].BaseURL) + + auth, err := os.ReadFile(authPath) + require.NoError(t, err) + assert.JSONEq(t, `{ + "auth_mode": "apikey", + "OPENAI_API_KEY": "my-api-key" + }`, string(auth)) +} + func TestCodexHarnessDefaultConfigDir(t *testing.T) { homePath, err := os.UserHomeDir() require.NoError(t, err) diff --git a/internal/harnesses/merge.go b/internal/harnesses/merge.go index 2e4d723..88a4450 100644 --- a/internal/harnesses/merge.go +++ b/internal/harnesses/merge.go @@ -53,9 +53,23 @@ func mergeJSONConfigFileWithOptions(path string, patch map[string]any, options m return data, nil } +func mergeOrCreateTOMLConfigFile(path string, patch map[string]any) (map[string]any, error) { + return mergeTOMLConfigFileWithOptions(path, patch, mergeOptions{ + AllowFileNotExists: true, + }) +} + func mergeTOMLConfigFile(path string, patch map[string]any) (map[string]any, error) { + return mergeTOMLConfigFileWithOptions(path, patch, mergeOptions{ + AllowFileNotExists: false, + }) +} + +func mergeTOMLConfigFileWithOptions(path string, patch map[string]any, options mergeOptions) (map[string]any, error) { dataBytes, err := os.ReadFile(path) - if err != nil { + if errors.Is(err, os.ErrNotExist) && options.AllowFileNotExists { + dataBytes = []byte{} + } else if err != nil { return nil, fmt.Errorf("failed to read file: %w", err) }