From 238ae8c45301cc1ad8ef338288720cee1f0ada93 Mon Sep 17 00:00:00 2001 From: Arpit Behera <50660083+arpitbehera@users.noreply.github.com> Date: Sat, 27 Jun 2026 12:38:17 +0300 Subject: [PATCH] feat(codex): use gpt-5.5 models (#41) Map Codex high usage to gpt-5.5 high and lower-cost tiers to gpt-5.5 low. Harden frontmatter parsing so LF and CRLF checkouts generate equivalent adapter output. --- adapters/claude-code/adapter.sh | 2 +- adapters/codex-cli/adapter.sh | 34 +++++----- adapters/gemini-cli/adapter.sh | 2 +- adapters/lib.sh | 2 + adapters/opencode/adapter.sh | 2 +- mcp/servers.yaml | 2 +- tests/adapters/claude-code/adapter.test.sh | 29 +++++++++ tests/adapters/codex-cli/adapter.test.sh | 75 +++++++++++++++++++--- tests/adapters/gemini-cli/adapter.test.sh | 28 ++++++++ tests/adapters/lib.test.sh | 23 +++++++ tests/adapters/opencode/adapter.test.sh | 29 +++++++++ tests/regression/run.sh | 4 +- 12 files changed, 202 insertions(+), 30 deletions(-) diff --git a/adapters/claude-code/adapter.sh b/adapters/claude-code/adapter.sh index 89b243b..6cdecc7 100755 --- a/adapters/claude-code/adapter.sh +++ b/adapters/claude-code/adapter.sh @@ -250,7 +250,7 @@ adapter_translate_agents() { echo "---" echo "name: $name" # Copy description block (may be folded YAML with continuation lines) - awk '/^---$/{n++; next} n==1 && /^description:/{print; in_desc=1; next} n==1 && in_desc && /^[[:space:]]/{print; next} n==1 && in_desc && !/^[[:space:]]/{in_desc=0} n>=2{exit}' "$agent" + awk '{ sub(/\r$/, "") } /^---$/{n++; next} n==1 && /^description:/{print; in_desc=1; next} n==1 && in_desc && /^[[:space:]]/{print; next} n==1 && in_desc && !/^[[:space:]]/{in_desc=0} n>=2{exit}' "$agent" echo "tools: $tools" echo "model: $model" echo "---" diff --git a/adapters/codex-cli/adapter.sh b/adapters/codex-cli/adapter.sh index be6c773..a19c273 100644 --- a/adapters/codex-cli/adapter.sh +++ b/adapters/codex-cli/adapter.sh @@ -296,13 +296,16 @@ adapter_translate_skills() { # _cc_toml_quote_key # Emits the TOML table key for [mcp_servers.]. -# Codex CLI requires MCP server names to match ^[a-zA-Z0-9_-]+$, so any -# character outside that set is replaced with a hyphen before writing. +# Safe identifiers can be emitted bare; names with spaces or punctuation must +# be quoted and escaped so the original MCP server name is preserved. _cc_toml_quote_key() { local name="$1" - # Sanitize: replace any character not in [a-zA-Z0-9_-] with a hyphen - local safe_name="${name//[^a-zA-Z0-9_-]/-}" - printf '[mcp_servers.%s]' "$safe_name" + if [[ "$name" =~ ^[A-Za-z0-9_-]+$ ]]; then + printf '[mcp_servers.%s]' "$name" + else + local escaped; escaped="$(_cc_toml_escape_string "$name")" + printf '[mcp_servers."%s"]' "$escaped" + fi } # _cc_toml_escape_string @@ -345,16 +348,16 @@ adapter_translate_config() { echo '' echo '# Inherit uses the top-level defaults; named profiles override them explicitly.' echo '[profiles.quality]' - echo 'model = "gpt-5.4"' + echo 'model = "gpt-5.5"' echo 'model_reasoning_effort = "high"' echo '' echo '[profiles.balanced]' - echo 'model = "gpt-5.4-mini"' - echo 'model_reasoning_effort = "medium"' + echo 'model = "gpt-5.5"' + echo 'model_reasoning_effort = "low"' echo '' echo '[profiles.budget]' - echo 'model = "gpt-5.3-codex-spark"' - echo 'model_reasoning_effort = "medium"' + echo 'model = "gpt-5.5"' + echo 'model_reasoning_effort = "low"' echo '' echo '[agents]' echo 'max_depth = 1' @@ -491,6 +494,7 @@ adapter_translate_config() { _cc_extract_description() { local file="$1" awk ' + { sub(/\r$/, "") } /^---$/ { fm++; next } fm == 1 && /^description:[[:space:]]*>/ { # Folded-block style: value starts on next indented lines @@ -539,10 +543,9 @@ _cc_toml_escape_dquote_string() { _cc_model_to_codex() { local model="$1" case "$model" in + gpt-5.4|gpt-5.4-mini|gpt-5.3-codex-spark) echo "gpt-5.5" ;; */*|gpt-*) echo "$model" ;; - low) echo "gpt-5.3-codex-spark" ;; - mid) echo "gpt-5.4-mini" ;; - high) echo "gpt-5.4" ;; + low|mid|high) echo "gpt-5.5" ;; *) echo "$model" ;; esac } @@ -552,8 +555,9 @@ _cc_model_to_codex() { _cc_model_reasoning_effort() { local model="$1" case "$model" in - high) echo "high" ;; - *) echo "medium" ;; + high|gpt-5.4) echo "high" ;; + low|mid|gpt-5.4-mini|gpt-5.3-codex-spark) echo "low" ;; + *) echo "medium" ;; esac } diff --git a/adapters/gemini-cli/adapter.sh b/adapters/gemini-cli/adapter.sh index ff3052e..795def2 100755 --- a/adapters/gemini-cli/adapter.sh +++ b/adapters/gemini-cli/adapter.sh @@ -143,7 +143,7 @@ adapter_translate_agents() { echo "---" echo "name: $name" # Copy description block (may be folded YAML) - awk '/^---$/{n++; next} n==1 && /^description:/{print; in_desc=1; next} n==1 && in_desc && /^[[:space:]]/{print; next} n==1 && in_desc && !/^[[:space:]]/{in_desc=0} n>=2{exit}' "$agent" + awk '{ sub(/\r$/, "") } /^---$/{n++; next} n==1 && /^description:/{print; in_desc=1; next} n==1 && in_desc && /^[[:space:]]/{print; next} n==1 && in_desc && !/^[[:space:]]/{in_desc=0} n>=2{exit}' "$agent" echo "tools:" printf '%s' "$tools_yaml" echo "model: $model_out" diff --git a/adapters/lib.sh b/adapters/lib.sh index 1dab156..a9e046a 100755 --- a/adapters/lib.sh +++ b/adapters/lib.sh @@ -41,6 +41,7 @@ rewrite_platform_paths() { parse_frontmatter() { local file="$1" key="$2" awk -v key="$key" ' + { sub(/\r$/, "") } /^---$/ { fm++; next } fm == 1 { # Match "key: value" — strip leading whitespace, capture value after first ":" @@ -123,6 +124,7 @@ parse_hook_yaml() { agent_body() { local file="$1" awk ' + { sub(/\r$/, "") } fm < 2 && /^---$/ { fm++; next } fm >= 2 { print } ' "$file" diff --git a/adapters/opencode/adapter.sh b/adapters/opencode/adapter.sh index 72259e1..ec81413 100755 --- a/adapters/opencode/adapter.sh +++ b/adapters/opencode/adapter.sh @@ -152,7 +152,7 @@ adapter_translate_agents() { { echo "---" # Copy description block verbatim (may be folded YAML with continuation lines) - awk '/^---$/{n++; next} n==1 && /^description:/{print; in_desc=1; next} n==1 && in_desc && /^[[:space:]]/{print; next} n==1 && in_desc && !/^[[:space:]]/{in_desc=0} n>=2{exit}' "$agent" + awk '{ sub(/\r$/, "") } /^---$/{n++; next} n==1 && /^description:/{print; in_desc=1; next} n==1 && in_desc && /^[[:space:]]/{print; next} n==1 && in_desc && !/^[[:space:]]/{in_desc=0} n>=2{exit}' "$agent" echo "mode: $mode_out" echo "model: $model_out" if [[ -z "$perms" ]]; then diff --git a/mcp/servers.yaml b/mcp/servers.yaml index 8c0f6c3..fb5a463 100755 --- a/mcp/servers.yaml +++ b/mcp/servers.yaml @@ -4,7 +4,7 @@ servers: url: "https://gmail.mcp.claude.com/mcp" env: {} exclude: [] - - name: Google-Calendar + - name: Google Calendar type: http url: "https://gcal.mcp.claude.com/mcp" env: {} diff --git a/tests/adapters/claude-code/adapter.test.sh b/tests/adapters/claude-code/adapter.test.sh index dfdf857..e3f963c 100755 --- a/tests/adapters/claude-code/adapter.test.sh +++ b/tests/adapters/claude-code/adapter.test.sh @@ -90,6 +90,35 @@ EOF return $result } +test_translate_agents_supports_crlf_frontmatter() { + local src; src="$(mktemp -d)" + local dst; dst="$(mktemp -d)" + mkdir -p "$src/agents" + printf '%s\r\n' \ + '---' \ + 'name: scribe' \ + 'description: >' \ + ' Test scribe' \ + ' across line endings' \ + 'model: mid' \ + 'mode: subagent' \ + 'capabilities: [read, write, edit]' \ + '---' \ + '' \ + 'You are the Scribe.' > "$src/agents/scribe.md" + adapter_translate_agents "$src/agents" "$dst" + local out="$dst/.claude/agents/scribe.md" + local result=0 + grep -q '^name: scribe' "$out" || { echo "name missing"; result=1; } + grep -q '^description: >' "$out" || { echo "description header missing"; result=1; } + grep -q '^ Test scribe' "$out" || { echo "description continuation missing"; result=1; } + grep -q '^model: sonnet' "$out" || { echo "model not mapped from CRLF frontmatter"; result=1; } + grep -q '^tools: Read, Glob, Grep, Write, Edit' "$out" || { echo "tools not mapped from CRLF capabilities"; result=1; } + grep -q '^You are the Scribe' "$out" || { echo "body missing"; result=1; } + rm -rf "$src" "$dst" + return $result +} + test_translate_agents_bash_capability() { local src; src="$(mktemp -d)" local dst; dst="$(mktemp -d)" diff --git a/tests/adapters/codex-cli/adapter.test.sh b/tests/adapters/codex-cli/adapter.test.sh index 32a7c48..7d5b4b0 100644 --- a/tests/adapters/codex-cli/adapter.test.sh +++ b/tests/adapters/codex-cli/adapter.test.sh @@ -21,6 +21,28 @@ _python_cmd() { echo "" } +# --------------------------------------------------------------------------- +# Model mapping tests +# --------------------------------------------------------------------------- + +test_cc_model_mapping_uses_gpt55_with_requested_reasoning() { + local result=0 + [[ "$(_cc_model_to_codex "high")" == "gpt-5.5" ]] || { echo "high tier model mapping failed"; result=1; } + [[ "$(_cc_model_reasoning_effort "high")" == "high" ]] || { echo "high tier reasoning mapping failed"; result=1; } + [[ "$(_cc_model_to_codex "mid")" == "gpt-5.5" ]] || { echo "mid tier model mapping failed"; result=1; } + [[ "$(_cc_model_reasoning_effort "mid")" == "low" ]] || { echo "mid tier reasoning mapping failed"; result=1; } + [[ "$(_cc_model_to_codex "low")" == "gpt-5.5" ]] || { echo "low tier model mapping failed"; result=1; } + [[ "$(_cc_model_reasoning_effort "low")" == "low" ]] || { echo "low tier reasoning mapping failed"; result=1; } + [[ "$(_cc_model_to_codex "gpt-5.4")" == "gpt-5.5" ]] || { echo "legacy gpt-5.4 model mapping failed"; result=1; } + [[ "$(_cc_model_reasoning_effort "gpt-5.4")" == "high" ]] || { echo "legacy gpt-5.4 reasoning mapping failed"; result=1; } + [[ "$(_cc_model_to_codex "gpt-5.4-mini")" == "gpt-5.5" ]] || { echo "legacy gpt-5.4-mini model mapping failed"; result=1; } + [[ "$(_cc_model_reasoning_effort "gpt-5.4-mini")" == "low" ]] || { echo "legacy gpt-5.4-mini reasoning mapping failed"; result=1; } + [[ "$(_cc_model_to_codex "gpt-5.3-codex-spark")" == "gpt-5.5" ]] || { echo "legacy gpt-5.3-codex-spark model mapping failed"; result=1; } + [[ "$(_cc_model_reasoning_effort "gpt-5.3-codex-spark")" == "low" ]] || { echo "legacy gpt-5.3-codex-spark reasoning mapping failed"; result=1; } + [[ "$(_cc_model_to_codex "openai/custom")" == "openai/custom" ]] || { echo "qualified passthrough failed"; result=1; } + return $result +} + # --------------------------------------------------------------------------- # Dispatcher tests # --------------------------------------------------------------------------- @@ -470,12 +492,14 @@ EOF local result=0 [[ "$content" == *'sandbox_workspace_write.network_access = false'* ]] || { echo "network_access default missing"; result=1; } [[ "$content" == *'[profiles.quality]'* ]] || { echo "profiles.quality missing"; result=1; } - [[ "$content" == *'model = "gpt-5.4"'* ]] || { echo "quality model missing"; result=1; } + [[ "$content" == *'model = "gpt-5.5"'* ]] || { echo "quality model missing"; result=1; } [[ "$content" == *'model_reasoning_effort = "high"'* ]] || { echo "quality reasoning effort missing"; result=1; } [[ "$content" == *'[profiles.balanced]'* ]] || { echo "profiles.balanced missing"; result=1; } - [[ "$content" == *'model = "gpt-5.4-mini"'* ]] || { echo "balanced model missing"; result=1; } + [[ "$content" == *'model = "gpt-5.5"'* ]] || { echo "balanced model missing"; result=1; } + [[ "$content" == *'model_reasoning_effort = "low"'* ]] || { echo "balanced reasoning effort missing"; result=1; } [[ "$content" == *'[profiles.budget]'* ]] || { echo "profiles.budget missing"; result=1; } - [[ "$content" == *'model = "gpt-5.3-codex-spark"'* ]] || { echo "budget model missing"; result=1; } + [[ "$content" == *'model = "gpt-5.5"'* ]] || { echo "budget model missing"; result=1; } + [[ "$content" == *'model_reasoning_effort = "low"'* ]] || { echo "budget reasoning effort missing"; result=1; } rm -rf "$src" "$dst" return $result } @@ -818,6 +842,39 @@ AGENTEOF return $result } +test_cc_translate_agent_toml_supports_crlf_frontmatter() { + local src; src="$(mktemp -d)" + local dst; dst="$(mktemp -d)" + mkdir -p "$src/agents" + printf '%s\r\n' \ + '---' \ + 'name: myagent' \ + 'description: >' \ + ' First line of description' \ + ' across line endings.' \ + 'mode: subagent' \ + 'capabilities: [read]' \ + 'model: low' \ + '---' \ + '' \ + 'Body text here.' > "$src/agents/myagent.md" + + adapter_translate_agent_toml "$src/agents/myagent.md" "$dst" + + local result=0 + local toml_file="$dst/.codex/agents/myagent.toml" + [[ -f "$toml_file" ]] || { echo "myagent.toml not created"; result=1; return $result; } + grep -q '^name = "myagent"$' "$toml_file" || { echo "name not parsed from CRLF frontmatter"; result=1; } + grep -q '^model = "gpt-5.5"$' "$toml_file" || { echo "model not mapped from CRLF frontmatter"; result=1; } + grep -q '^model_reasoning_effort = "low"$' "$toml_file" || { echo "reasoning not mapped from CRLF frontmatter"; result=1; } + grep -q 'First line of description' "$toml_file" || { echo "description missing first CRLF line"; result=1; } + grep -q 'across line endings' "$toml_file" || { echo "description missing continuation CRLF line"; result=1; } + grep -q 'Body text here.' "$toml_file" || { echo "body missing"; result=1; } + + rm -rf "$src" "$dst" + return $result +} + test_cc_translate_agent_toml_no_platform_paths_in_output() { # .platform/ and DISPATCHER.md must not appear in the generated TOML local src; src="$(mktemp -d)" @@ -1076,14 +1133,14 @@ test_cc_adapter_build_real_agent_corpus_has_required_fields_and_metadata() { || { echo 'postman.toml should be workspace-write'; result=1; } grep -q '^sandbox_mode = "workspace-write"$' "$dst/.codex/agents/architect.toml" \ || { echo 'architect.toml should be workspace-write'; result=1; } - grep -q '^model = "gpt-5.4"$' "$dst/.codex/agents/architect.toml" \ - || { echo 'architect.toml should map to gpt-5.4'; result=1; } + grep -q '^model = "gpt-5.5"$' "$dst/.codex/agents/architect.toml" \ + || { echo 'architect.toml should map to gpt-5.5'; result=1; } grep -q '^model_reasoning_effort = "high"$' "$dst/.codex/agents/architect.toml" \ || { echo 'architect.toml should map to high reasoning effort'; result=1; } - grep -q '^model = "gpt-5.4-mini"$' "$dst/.codex/agents/scribe.toml" \ - || { echo 'scribe.toml should map to gpt-5.4-mini'; result=1; } - grep -q '^model_reasoning_effort = "medium"$' "$dst/.codex/agents/scribe.toml" \ - || { echo 'scribe.toml should map to medium reasoning effort'; result=1; } + grep -q '^model = "gpt-5.5"$' "$dst/.codex/agents/scribe.toml" \ + || { echo 'scribe.toml should map to gpt-5.5'; result=1; } + grep -q '^model_reasoning_effort = "low"$' "$dst/.codex/agents/scribe.toml" \ + || { echo 'scribe.toml should map to low reasoning effort'; result=1; } rm -rf "$dst" return $result diff --git a/tests/adapters/gemini-cli/adapter.test.sh b/tests/adapters/gemini-cli/adapter.test.sh index e16c2cf..d7c56a4 100755 --- a/tests/adapters/gemini-cli/adapter.test.sh +++ b/tests/adapters/gemini-cli/adapter.test.sh @@ -106,6 +106,34 @@ HEREDOC return $result } +test_gemini_translate_agents_supports_crlf_frontmatter() { + local src; src="$(mktemp -d)" + local dst; dst="$(mktemp -d)" + mkdir -p "$src/agents" + printf '%s\r\n' \ + '---' \ + 'name: scribe' \ + 'description: >' \ + ' Test scribe' \ + ' across line endings' \ + 'model: mid' \ + 'capabilities: [read, write, edit]' \ + '---' \ + '' \ + 'You are the Scribe.' > "$src/agents/scribe.md" + adapter_translate_agents "$src/agents" "$dst" + local out="$dst/.gemini/agents/scribe.md" + local result=0 + grep -q '^name: scribe' "$out" || { echo "name missing"; result=1; } + grep -q '^description: >' "$out" || { echo "description header missing"; result=1; } + grep -q '^ Test scribe' "$out" || { echo "description continuation missing"; result=1; } + grep -q '^model: gemini-2.5-flash' "$out" || { echo "model not mapped from CRLF frontmatter"; result=1; } + grep -q '^ *- read_file' "$out" || { echo "tools not mapped from CRLF capabilities"; result=1; } + grep -q '^You are the Scribe' "$out" || { echo "body missing"; result=1; } + rm -rf "$src" "$dst" + return $result +} + test_gemini_translate_agents_bash_capability() { local src; src="$(mktemp -d)" local dst; dst="$(mktemp -d)" diff --git a/tests/adapters/lib.test.sh b/tests/adapters/lib.test.sh index 41d0c51..d971792 100755 --- a/tests/adapters/lib.test.sh +++ b/tests/adapters/lib.test.sh @@ -22,6 +22,29 @@ EOF [[ "$result" == "scribe" ]] || { echo "expected 'scribe', got '$result'"; return 1; } } +test_parse_frontmatter_and_body_support_crlf() { + local fixture; fixture="$(mktemp)" + printf '%s\r\n' \ + '---' \ + 'name: architect' \ + 'model: high' \ + 'capabilities: [read, write]' \ + '---' \ + 'You are the Architect.' > "$fixture" + local name model capabilities body + name="$(parse_frontmatter "$fixture" name)" + model="$(parse_frontmatter "$fixture" model)" + capabilities="$(parse_capabilities "$fixture")" + body="$(agent_body "$fixture")" + rm "$fixture" + local result=0 + [[ "$name" == "architect" ]] || { echo "expected name architect, got '$name'"; result=1; } + [[ "$model" == "high" ]] || { echo "expected model high, got '$model'"; result=1; } + [[ "$capabilities" == "read write" ]] || { echo "expected capabilities 'read write', got '$capabilities'"; result=1; } + [[ "$body" == "You are the Architect." ]] || { echo "expected CRLF body, got '$body'"; result=1; } + return $result +} + test_parse_frontmatter_list() { local fixture; fixture="$(mktemp)" cat > "$fixture" <<'EOF' diff --git a/tests/adapters/opencode/adapter.test.sh b/tests/adapters/opencode/adapter.test.sh index bda04d8..e330489 100755 --- a/tests/adapters/opencode/adapter.test.sh +++ b/tests/adapters/opencode/adapter.test.sh @@ -149,6 +149,35 @@ EOF return $result } +test_oc_translate_agents_supports_crlf_frontmatter() { + local src; src="$(mktemp -d)" + local dst; dst="$(mktemp -d)" + mkdir -p "$src/agents" + printf '%s\r\n' \ + '---' \ + 'name: scribe' \ + 'description: >' \ + ' Test scribe' \ + ' across line endings' \ + 'model: mid' \ + 'mode: subagent' \ + 'capabilities: [read, write, edit]' \ + '---' \ + '' \ + 'You are the Scribe.' > "$src/agents/scribe.md" + adapter_translate_agents "$src/agents" "$dst" + local out="$dst/.opencode/agents/scribe.md" + local result=0 + grep -q '^description: >' "$out" || { echo "description header missing"; result=1; } + grep -q '^ Test scribe' "$out" || { echo "description continuation missing"; result=1; } + grep -q '^mode: subagent' "$out" || { echo "mode not parsed from CRLF frontmatter"; result=1; } + grep -q '^model: anthropic/claude-sonnet-4-5' "$out" || { echo "model not mapped from CRLF frontmatter"; result=1; } + grep -q '^ edit: allow' "$out" || { echo "permissions not mapped from CRLF capabilities"; result=1; } + grep -q '^You are the Scribe' "$out" || { echo "body missing"; result=1; } + rm -rf "$src" "$dst" + return $result +} + test_oc_translate_agents_rewrites_body_paths() { local src; src="$(mktemp -d)" local dst; dst="$(mktemp -d)" diff --git a/tests/regression/run.sh b/tests/regression/run.sh index f129974..add6ab8 100755 --- a/tests/regression/run.sh +++ b/tests/regression/run.sh @@ -53,9 +53,9 @@ echo "File lists match." echo "── Per-file comparison ──" FAILED=0 while IFS= read -r f; do - if ! diff -q "$SNAPSHOT_DIR/$f" "$DIST_DIR/$f" >/dev/null 2>&1; then + if ! diff --strip-trailing-cr -q "$SNAPSHOT_DIR/$f" "$DIST_DIR/$f" >/dev/null 2>&1; then echo "DIFF: $f" - diff "$SNAPSHOT_DIR/$f" "$DIST_DIR/$f" | head -20 + diff --strip-trailing-cr "$SNAPSHOT_DIR/$f" "$DIST_DIR/$f" | head -20 FAILED=$((FAILED + 1)) fi done < "$SNAP_LIST"