Download .forge/skills/test-reasoning/scripts/test-reasoning.sh from SaylorTwift/forgecode: direct link, hf CLI and curl.
- Browser
- Download file 19.1 kB
-
https://huggingface.co/SaylorTwift/forgecode/resolve/main/.forge/skills/test-reasoning/scripts/test-reasoning.sh
- Command line
-
hf download hf://SaylorTwift/forgecode/.forge/skills/test-reasoning/scripts/test-reasoning.sh
-
curl -L -o test-reasoning.sh https://huggingface.co/SaylorTwift/forgecode/resolve/main/.forge/skills/test-reasoning/scripts/test-reasoning.sh
19.1 kB
| # scripts/test-reasoning.sh | |
| # | |
| # Validates that reasoning parameters are correctly serialized for each | |
| # provider across all supported effort levels. | |
| # | |
| # Usage: ./scripts/test-reasoning.sh | |
| set -uo pipefail | |
| # βββ colors βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ | |
| BOLD='\033[1m' | |
| RESET='\033[0m' | |
| GREEN='\033[32m' | |
| RED='\033[31m' | |
| YELLOW='\033[33m' | |
| CYAN='\033[36m' | |
| DIM='\033[2m' | |
| # βββ state ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ | |
| PASS=0 | |
| FAIL=0 | |
| SKIP=0 | |
| BINARY="target/debug/forge" | |
| WORK_DIR="$(mktemp -d)" | |
| SEQ=0 | |
| RESULT_FILES=() | |
| CURRENT_RF="" | |
| cleanup() { rm -rf "$WORK_DIR"; } | |
| trap cleanup EXIT | |
| # βββ output helpers βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ | |
| # Each helper writes a tagged line to stdout. Within a background subshell, | |
| # stdout is redirected to a per-job result file; the main process reads it back | |
| # after wait to tally counts and emit colour output in the original order. | |
| log_header() { printf "HEADER\t%s\n" "$1"; } | |
| log_pass() { printf "PASS\t%s\n" "$1"; } | |
| log_fail() { printf "FAIL\t%s\n" "$1"; } | |
| log_skip() { printf "SKIP\t%s\n" "$1"; } | |
| # βββ json helpers βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ | |
| # json_get <file> <dot.separated.path> | |
| # Prints the JSON value at the given path, or "null" if absent/null. | |
| # Uses raw_decode to parse only the first JSON object in the file, which | |
| # correctly handles both single-document JSON and NDJSON (even when multiple | |
| # objects appear on the same line without a newline separator). | |
| json_get() { | |
| python3 - "$1" "$2" <<'PY' | |
| import json, sys | |
| with open(sys.argv[1]) as f: | |
| raw = f.read().strip() | |
| # raw_decode stops after the first complete JSON value regardless of trailing | |
| # content (extra objects, newlines, null bytes, etc.). | |
| decoder = json.JSONDecoder() | |
| d, _ = decoder.raw_decode(raw) | |
| keys = sys.argv[2].split('.') | |
| v = d | |
| for k in keys: | |
| v = v.get(k) if isinstance(v, dict) else None | |
| if v is None: | |
| break | |
| print(json.dumps(v)) | |
| PY | |
| } | |
| # assert_field <file> <dot.path> <expected_json_value> <label> | |
| assert_field() { | |
| local file="$1" path="$2" expected="$3" label="$4" | |
| local actual | |
| actual="$(json_get "$file" "$path")" | |
| if [ "$actual" = "$expected" ]; then | |
| log_pass "$label $path = $expected" | |
| else | |
| log_fail "$label $path β expected $expected, got $actual" | |
| fi | |
| } | |
| # βββ test runner ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ | |
| # run_test <outfile> <provider_id> <model_id> [KEY=VALUE ...] | |
| # Runs forge with FORGE_DEBUG_REQUESTS pointing to <outfile>. | |
| # Extra KEY=VALUE arguments are forwarded as additional env vars. | |
| # Returns 0 if the request file was written, 1 otherwise (e.g. auth missing). | |
| run_test() { | |
| local out="$1" provider="$2" model="$3" | |
| shift 3 | |
| env FORGE_DEBUG_REQUESTS="$out" \ | |
| FORGE_SESSION__PROVIDER_ID="$provider" \ | |
| FORGE_SESSION__MODEL_ID="$model" \ | |
| "$@" \ | |
| "$BINARY" -p "Hello!" >/dev/null 2>&1 || true | |
| [ -f "$out" ] | |
| } | |
| # run_test_expect_failure <outfile> <provider_id> <model_id> [KEY=VALUE ...] | |
| # Like run_test, but expects forge to exit non-zero and NOT write the request file. | |
| # Invalid config values (e.g. unknown Effort variant) are rejected at startup, | |
| # before any provider interaction, so this check is independent of credentials. | |
| # Returns 0 if forge exited non-zero and wrote no file, 1 otherwise. | |
| run_test_expect_failure() { | |
| local out="$1" provider="$2" model="$3" | |
| shift 3 | |
| env FORGE_DEBUG_REQUESTS="$out" \ | |
| FORGE_SESSION__PROVIDER_ID="$provider" \ | |
| FORGE_SESSION__MODEL_ID="$model" \ | |
| "$@" \ | |
| "$BINARY" -p "Hello!" >/dev/null 2>&1 | |
| local status=$? | |
| [ "$status" -ne 0 ] && [ ! -f "$out" ] | |
| } | |
| # βββ parallel job launcher ββββββββββββββββββββββββββββββββββββββββββββββββββββ | |
| # next_result_file β allocates the next ordered result file path, appends it to | |
| # RESULT_FILES, increments SEQ, and stores the path in CURRENT_RF. | |
| # Must be called in the main process (NOT inside $()) so that RESULT_FILES and | |
| # SEQ are updated in the parent shell. | |
| next_result_file() { | |
| CURRENT_RF="$WORK_DIR/result-$(printf '%05d' "$SEQ").txt" | |
| RESULT_FILES+=("$CURRENT_RF") | |
| SEQ=$((SEQ + 1)) | |
| } | |
| # βββ build ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ | |
| printf "${BOLD}Reasoning Serialization Tests${RESET}\n" | |
| printf "${DIM}Building forge (debug)...${RESET}\n\n" | |
| if ! cargo build 2>&1 | grep -E "^error|Finished|^ Compiling forge_main"; then | |
| printf "${RED}Build failed β aborting.${RESET}\n" | |
| exit 1 | |
| fi | |
| # βββ OpenRouter Β· openai/o4-mini β effort levels βββββββββββββββββββββββββββββ | |
| # OpenRouter passes reasoning.effort straight through. | |
| # Valid effort values: none Β· minimal Β· low Β· medium Β· high Β· xhigh | |
| # Note: the default forge config sets reasoning.enabled=true; it always appears | |
| # alongside any explicit effort. max_tokens and exclude remain absent. | |
| # Ref: https://openrouter.ai/docs/guides/best-practices/reasoning-tokens | |
| for effort in none minimal low medium high xhigh; do | |
| next_result_file | |
| ( | |
| log_header "OpenRouter Β· openai/o4-mini Β· effort: $effort" | |
| OUT="$WORK_DIR/openrouter-openai-effort-$effort.json" | |
| if run_test "$OUT" open_router "openai/o4-mini" "FORGE_REASONING__EFFORT=$effort"; then | |
| assert_field "$OUT" "reasoning.effort" "\"$effort\"" "openrouter/openai" | |
| assert_field "$OUT" "reasoning.max_tokens" "null" "openrouter/openai" | |
| assert_field "$OUT" "reasoning.exclude" "null" "openrouter/openai" | |
| else | |
| log_skip "open_router not configured β skipping" | |
| fi | |
| ) > "$CURRENT_RF" & | |
| done | |
| # βββ OpenRouter Β· openai/o4-mini β max_tokens ββββββββββββββββββββββββββββββββ | |
| # When max_tokens is set, reasoning.max_tokens should appear. | |
| # Note: the default forge config also injects effort="medium" and enabled=true; | |
| # only max_tokens itself is verified here. | |
| next_result_file | |
| ( | |
| log_header "OpenRouter Β· openai/o4-mini Β· max_tokens: 4000" | |
| OUT="$WORK_DIR/openrouter-openai-max-tokens.json" | |
| if run_test "$OUT" open_router "openai/o4-mini" FORGE_REASONING__MAX_TOKENS=4000; then | |
| assert_field "$OUT" "reasoning.max_tokens" "4000" "openrouter/openai" | |
| else | |
| log_skip "open_router not configured β skipping" | |
| fi | |
| ) > "$CURRENT_RF" & | |
| # βββ OpenRouter Β· openai/o4-mini β exclude βββββββββββββββββββββββββββββββββββ | |
| # When exclude=true, reasoning runs internally but is omitted from the response. | |
| next_result_file | |
| ( | |
| log_header "OpenRouter Β· openai/o4-mini Β· effort: high + exclude: true" | |
| OUT="$WORK_DIR/openrouter-openai-exclude.json" | |
| if run_test "$OUT" open_router "openai/o4-mini" \ | |
| FORGE_REASONING__EFFORT=high FORGE_REASONING__EXCLUDE=true; then | |
| assert_field "$OUT" "reasoning.effort" '"high"' "openrouter/openai" | |
| assert_field "$OUT" "reasoning.exclude" "true" "openrouter/openai" | |
| else | |
| log_skip "open_router not configured β skipping" | |
| fi | |
| ) > "$CURRENT_RF" & | |
| # βββ OpenRouter Β· openai/o4-mini β enabled βββββββββββββββββββββββββββββββββββ | |
| # enabled=true activates reasoning. The default config already sets enabled=true | |
| # and effort="medium"; this test confirms enabled is explicitly passed through. | |
| next_result_file | |
| ( | |
| log_header "OpenRouter Β· openai/o4-mini Β· enabled: true" | |
| OUT="$WORK_DIR/openrouter-openai-enabled.json" | |
| if run_test "$OUT" open_router "openai/o4-mini" FORGE_REASONING__ENABLED=true; then | |
| assert_field "$OUT" "reasoning.enabled" "true" "openrouter/openai" | |
| assert_field "$OUT" "reasoning.exclude" "null" "openrouter/openai" | |
| else | |
| log_skip "open_router not configured β skipping" | |
| fi | |
| ) > "$CURRENT_RF" & | |
| # βββ OpenRouter Β· anthropic/claude-opus-4-5 β max_tokens βββββββββββββββββββββ | |
| # For Anthropic models via OpenRouter, max_tokens maps to budget_tokens. | |
| # Valid range: integer >= 1024 | |
| # Note: default config injects effort="medium" and enabled=true alongside max_tokens. | |
| next_result_file | |
| ( | |
| log_header "OpenRouter Β· anthropic/claude-opus-4-5 Β· max_tokens: 4000" | |
| OUT="$WORK_DIR/openrouter-anthropic-max-tokens.json" | |
| if run_test "$OUT" open_router "anthropic/claude-opus-4-5" FORGE_REASONING__MAX_TOKENS=4000; then | |
| assert_field "$OUT" "reasoning.max_tokens" "4000" "openrouter/anthropic" | |
| else | |
| log_skip "open_router not configured β skipping" | |
| fi | |
| ) > "$CURRENT_RF" & | |
| # βββ OpenRouter Β· moonshotai/kimi-k2 β max_tokens ββββββββββββββββββββββββββββ | |
| # Kimi K2 uses token-budget reasoning via OpenRouter (reasoning.max_tokens). | |
| # Valid range: integer >= 1024 | |
| next_result_file | |
| ( | |
| log_header "OpenRouter Β· moonshotai/kimi-k2 Β· max_tokens: 4000" | |
| OUT="$WORK_DIR/openrouter-kimi-max-tokens.json" | |
| if run_test "$OUT" open_router "moonshotai/kimi-k2" FORGE_REASONING__MAX_TOKENS=4000; then | |
| assert_field "$OUT" "reasoning.max_tokens" "4000" "openrouter/kimi-k2" | |
| else | |
| log_skip "open_router not configured β skipping" | |
| fi | |
| ) > "$CURRENT_RF" & | |
| next_result_file | |
| ( | |
| log_header "OpenRouter Β· moonshotai/kimi-k2 Β· effort: high" | |
| OUT="$WORK_DIR/openrouter-kimi-effort-high.json" | |
| if run_test "$OUT" open_router "moonshotai/kimi-k2" FORGE_REASONING__EFFORT=high; then | |
| assert_field "$OUT" "reasoning.effort" '"high"' "openrouter/kimi-k2" | |
| else | |
| log_skip "open_router not configured β skipping" | |
| fi | |
| ) > "$CURRENT_RF" & | |
| # βββ OpenRouter Β· minimax/minimax-m2 β max_tokens ββββββββββββββββββββββββββββ | |
| # MiniMax M2 uses token-budget reasoning via OpenRouter; maps to thinking_budget. | |
| # Valid range: integer >= 1024 | |
| next_result_file | |
| ( | |
| log_header "OpenRouter Β· minimax/minimax-m2 Β· max_tokens: 4000" | |
| OUT="$WORK_DIR/openrouter-minimax-max-tokens.json" | |
| if run_test "$OUT" open_router "minimax/minimax-m2" FORGE_REASONING__MAX_TOKENS=4000; then | |
| assert_field "$OUT" "reasoning.max_tokens" "4000" "openrouter/minimax-m2" | |
| else | |
| log_skip "open_router not configured β skipping" | |
| fi | |
| ) > "$CURRENT_RF" & | |
| next_result_file | |
| ( | |
| log_header "OpenRouter Β· minimax/minimax-m2 Β· effort: high" | |
| OUT="$WORK_DIR/openrouter-minimax-effort-high.json" | |
| if run_test "$OUT" open_router "minimax/minimax-m2" FORGE_REASONING__EFFORT=high; then | |
| assert_field "$OUT" "reasoning.effort" '"high"' "openrouter/minimax-m2" | |
| else | |
| log_skip "open_router not configured β skipping" | |
| fi | |
| ) > "$CURRENT_RF" & | |
| # βββ Anthropic Β· claude-opus-4-6 β effort levels βββββββββββββββββββββββββββββ | |
| # Newer models use output_config.effort instead of the thinking object. | |
| # Valid effort values: low Β· medium Β· high Β· max (max is opus-4-6 only) | |
| # Ref: https://platform.claude.com/docs/en/build-with-claude/effort | |
| for effort in low medium high max; do | |
| next_result_file | |
| ( | |
| log_header "Anthropic Β· claude-opus-4-6 Β· effort: $effort" | |
| OUT="$WORK_DIR/anthropic-opus46-effort-$effort.json" | |
| if run_test "$OUT" anthropic "claude-opus-4-6" "FORGE_REASONING__EFFORT=$effort"; then | |
| assert_field "$OUT" "output_config.effort" "\"$effort\"" "anthropic/opus4.6" | |
| assert_field "$OUT" "thinking" "null" "anthropic/opus4.6" | |
| else | |
| log_skip "anthropic not configured β skipping" | |
| fi | |
| ) > "$CURRENT_RF" & | |
| done | |
| # βββ Anthropic Β· claude-3-7-sonnet-20250219 β thinking object ββββββββββββββββ | |
| # Older models use the thinking object with budget_tokens instead of effort. | |
| # budget_tokens must be > 1024 and < max_tokens. | |
| # Ref: https://platform.claude.com/docs/en/build-with-claude/effort | |
| next_result_file | |
| ( | |
| log_header "Anthropic Β· claude-3-7-sonnet-20250219 Β· enabled: true + max_tokens: 8000" | |
| OUT="$WORK_DIR/anthropic-sonnet37-thinking.json" | |
| if run_test "$OUT" anthropic "claude-3-7-sonnet-20250219" \ | |
| FORGE_REASONING__ENABLED=true FORGE_REASONING__MAX_TOKENS=8000; then | |
| assert_field "$OUT" "thinking.type" '"enabled"' "anthropic/sonnet3.7" | |
| assert_field "$OUT" "thinking.budget_tokens" "8000" "anthropic/sonnet3.7" | |
| assert_field "$OUT" "output_config" "null" "anthropic/sonnet3.7" | |
| else | |
| log_skip "anthropic not configured β skipping" | |
| fi | |
| ) > "$CURRENT_RF" & | |
| # βββ GitHub Copilot Β· o4-mini β effort levels ββββββββββββββββββββββββββββββββ | |
| # Chat Completions API serializes reasoning as a top-level reasoning_effort string. | |
| # Valid effort values: none Β· minimal Β· low Β· medium Β· high Β· xhigh | |
| # Ref: https://developers.openai.com/api/reference/resources/chat/subresources/completions/methods/create | |
| for effort in none minimal low medium high xhigh; do | |
| next_result_file | |
| ( | |
| log_header "GitHub Copilot Β· o4-mini Β· effort: $effort" | |
| OUT="$WORK_DIR/github-copilot-effort-$effort.json" | |
| if run_test "$OUT" github_copilot "o4-mini" "FORGE_REASONING__EFFORT=$effort"; then | |
| assert_field "$OUT" "reasoning_effort" "\"$effort\"" "github_copilot/o4-mini" | |
| assert_field "$OUT" "reasoning" "null" "github_copilot/o4-mini" | |
| else | |
| log_skip "github_copilot not configured β skipping" | |
| fi | |
| ) > "$CURRENT_RF" & | |
| done | |
| # βββ Codex Β· gpt-5.1-codex β effort levels βββββββββββββββββββββββββββββββββββ | |
| # Responses API uses a nested reasoning object with effort + summary fields. | |
| # Each effort value is passed through as-is; summary defaults to "auto". | |
| # Ref: https://developers.openai.com/api/docs/guides/reasoning | |
| for effort in none minimal low medium high xhigh; do | |
| next_result_file | |
| ( | |
| log_header "Codex Β· gpt-5.1-codex Β· effort: $effort" | |
| OUT="$WORK_DIR/codex-effort-$effort.json" | |
| if run_test "$OUT" codex "gpt-5.1-codex" "FORGE_REASONING__EFFORT=$effort"; then | |
| assert_field "$OUT" "reasoning.effort" "\"$effort\"" "codex/gpt-5.1-codex" | |
| assert_field "$OUT" "reasoning.summary" '"auto"' "codex/gpt-5.1-codex" | |
| assert_field "$OUT" "reasoning_effort" "null" "codex/gpt-5.1-codex" | |
| else | |
| log_skip "codex not configured β skipping" | |
| fi | |
| ) > "$CURRENT_RF" & | |
| done | |
| # βββ Codex Β· gpt-5.1-codex β exclude ββββββββββββββββββββββββββββββββββββββββ | |
| # When exclude=true the effort is passed through unchanged and summary="concise". | |
| next_result_file | |
| ( | |
| log_header "Codex Β· gpt-5.1-codex Β· effort: medium + exclude: true" | |
| OUT="$WORK_DIR/codex-exclude.json" | |
| if run_test "$OUT" codex "gpt-5.1-codex" \ | |
| FORGE_REASONING__EFFORT=medium FORGE_REASONING__EXCLUDE=true; then | |
| assert_field "$OUT" "reasoning.effort" '"medium"' "codex/gpt-5.1-codex" | |
| assert_field "$OUT" "reasoning.summary" '"concise"' "codex/gpt-5.1-codex" | |
| else | |
| log_skip "codex not configured β skipping" | |
| fi | |
| ) > "$CURRENT_RF" & | |
| # βββ Invalid effort β config parse error (one per provider) ββββββββββββββββββ | |
| # "invalid" is not a recognised Effort variant. Forge must reject it at config | |
| # parse time, exit non-zero, and never write a debug request file. | |
| # This check runs regardless of provider credentials. | |
| next_result_file | |
| ( | |
| log_header "Invalid effort Β· config parse error" | |
| for entry in \ | |
| "open_router:openai/o4-mini" \ | |
| "anthropic:claude-opus-4-6" \ | |
| "github_copilot:o4-mini" \ | |
| "codex:gpt-5.1-codex" | |
| do | |
| provider="${entry%%:*}" | |
| model="${entry##*:}" | |
| OUT="$WORK_DIR/invalid-effort-${provider}.json" | |
| if run_test_expect_failure "$OUT" "$provider" "$model" FORGE_REASONING__EFFORT=invalid; then | |
| log_pass "$provider/$model invalid effort β non-zero exit, no request written" | |
| else | |
| log_fail "$provider/$model invalid effort was not rejected" | |
| fi | |
| done | |
| ) > "$CURRENT_RF" & | |
| # βββ collect results ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ | |
| wait # wait for all background jobs to finish | |
| for f in "${RESULT_FILES[@]}"; do | |
| [ -f "$f" ] || continue | |
| while IFS=$'\t' read -r type msg; do | |
| case "$type" in | |
| HEADER) printf "\n${BOLD}${CYAN}βΆ %s${RESET}\n" "$msg" ;; | |
| PASS) printf " ${GREEN}β${RESET} %s\n" "$msg"; PASS=$((PASS + 1)) ;; | |
| FAIL) printf " ${RED}β${RESET} %s\n" "$msg"; FAIL=$((FAIL + 1)) ;; | |
| SKIP) printf " ${YELLOW}~${RESET} %s\n" "$msg"; SKIP=$((SKIP + 1)) ;; | |
| esac | |
| done < "$f" | |
| done | |
| # βββ summary ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ | |
| printf "\n${BOLD}βββββββββββββββββββββββββββββββββββββββββ${RESET}\n" | |
| printf "${BOLD}Results:${RESET} " | |
| printf "${GREEN}%d passed${RESET} " "$PASS" | |
| [ "$FAIL" -gt 0 ] && printf "${RED}%d failed${RESET} " "$FAIL" \ | |
| || printf "${DIM}%d failed${RESET} " "$FAIL" | |
| printf "${YELLOW}%d skipped${RESET}\n\n" "$SKIP" | |
| [ "$FAIL" -eq 0 ] | |