Files
magpie/internal/agent/codex_headers_table_test.go
yetone e66fa16593 codex: magpie's actor header goes into a [model_providers.magpie.http_headers] table the config already has, instead of failing every model switch (wztlink1013 on Discord)
wztlink1013 (v0.1.1134, Windows, Codex 0.162.0) could not switch Codex's
model on the Agents page: "config.toml: edited TOML is invalid: toml: key
http_headers should be a table, not a value".

Cause: putProvider rewrites [model_providers.magpie] whole with
http_headers = { "x-openai-actor-authorization" = "magpie" }. A config
that holds the headers as their own [model_providers.magpie.http_headers]
table keeps that table (a separate block the rewrite leaves), so the file
defined http_headers twice and writeTOML refused it. Reproduced in a temp
dir with exactly that message, in LF and CRLF files alike, so line
endings are not the cause. With only the subtable and no parent table the
same edit failed as "key http_headers is already defined". Dotted keys
inside the table (http_headers."x-org" = …) did not fail but were
dropped, the user's other headers with them.

Fix: edit.Inline, an inline table SetTOMLTable merges into the spelling
the file has: in [name.key] each entry is set with SetTOMLKey's in-place
edit (other headers and comments kept); among dotted keys their lines are
kept as written and each entry set there; otherwise written inline,
byte for byte as before. putProvider passes the header as an Inline.
SetTOMLKey's body moved into setTOMLKeyLines so both share it.

Siblings checked: no other agent writes an inline table with
SetTOMLTable (kimi's providers.magpie and Codex's thread tables are
scalars only); the MCP writer (putCodex) already removes a server's
subtables before writing it. Not changed: a magpie provider spelled by
dotted keys outside its own table ([model_providers] magpie.x = …, or at
the top) still fails as "table magpie already exists"; that is not this
report's shape.

Run:
- TestSetTOMLTableInlineMergesUsersSpelling (internal/edit): own table,
  own table without the header, own table only, dotted keys (with and
  without the header), inline, absent; each LF and CRLF, checked by
  decoding the result. Red with the subtable branch disabled (the
  reported error) and with the dotted branch disabled (user's x-org lost).
- TestCodexMagpieHeadersAsOwnTable (internal/agent): the reporter's shape
  through codex().Fields[0].Set, LF and CRLF. Red with putProvider back
  on edit.Raw: "key http_headers should be a table, not a value".
- gofmt -l (clean); go vet, GOOS=linux go vet -tags nogui, GOOS=windows
  go vet (with and without nogui) on internal/edit and internal/agent;
  go build for darwin, windows and linux (nogui).
- go test -tags nogui ./internal/edit ./internal/agent ./internal/library
  ./internal/provider under a temp HOME with pinned caches: all ok.
- codex-cli 0.161.0 (CODEX_HOME in a temp dir, codex mcp list) loads the
  merged config. Not tried on the Windows box or Codex 0.162.0.
2026-10-09 13:50:53 +08:00

45 lines
1.8 KiB
Go

package agent
import (
"strings"
"testing"
)
// wztlink1013 on Discord (Windows, Codex 0.162.0): switching Codex's model
// on the Agents page failed with "config.toml: edited TOML is invalid: toml:
// key http_headers should be a table, not a value". The config held magpie's
// provider headers as their own table, which a rewrite of
// [model_providers.magpie] leaves in place, and the inline http_headers
// written beside it defined the key twice. The header is now set in that
// table, the user's other headers and comments kept.
func TestCodexMagpieHeadersAsOwnTable(t *testing.T) {
for _, crlf := range []bool{false, true} {
config := "model = \"gpt-5.5\"\n\n" +
"[model_providers.magpie]\nname = \"magpie\"\nbase_url = \"http://127.0.0.1:1/v1\"\nwire_api = \"responses\"\n\n" +
"[model_providers.magpie.http_headers]\n# my proxy\n\"x-org\" = \"acme\"\n\"x-openai-actor-authorization\" = \"magpie\"\n"
if crlf {
config = strings.ReplaceAll(config, "\n", "\r\n")
}
home, read := codexHome(t, `{"OPENAI_API_KEY":"sk-relay"}`, config)
cx := codex(home)
if err := cx.Fields[0].Set("fake/m1"); err != nil {
t.Fatalf("crlf %v: %v", crlf, err)
}
cfg := strings.ReplaceAll(read(), "\r\n", "\n")
if strings.Contains(cfg, "http_headers = {") {
t.Fatalf("crlf %v: inline http_headers beside the table:\n%s", crlf, cfg)
}
for _, w := range []string{"[model_providers.magpie.http_headers]", "# my proxy", `"x-org" = "acme"`, `"x-openai-actor-authorization" = "magpie"`} {
if !strings.Contains(cfg, w) {
t.Fatalf("crlf %v: no %q:\n%s", crlf, w, cfg)
}
}
if strings.Count(cfg, "x-openai-actor-authorization") != 1 {
t.Fatalf("crlf %v: header written twice:\n%s", crlf, cfg)
}
if c := cx.Check(); c != "" {
t.Fatalf("crlf %v: %s", crlf, c)
}
}
}