diff --git a/install.ps1 b/install.ps1 index 8635f0a..11fbab8 100644 --- a/install.ps1 +++ b/install.ps1 @@ -386,6 +386,10 @@ if ($ProxyBin) { $yaml += ' allow-remote: false' $yaml += ' disable-control-panel: true' $yaml += 'debug: false' + $yaml += '# Model-list cloaking would rewrite ids into reversed pseudo-claude names;' + $yaml += '# our anthropic-compat/ aliases pass the picker filter with readable names.' + $yaml += 'claude-code:' + $yaml += ' disable-cloaking-model-list: true' $yaml += 'claude-api-key:' $yaml += " - api-key: `"$ApiKey`"" $yaml += " base-url: `"$GatewayUrl`"" @@ -401,8 +405,12 @@ if ($ProxyBin) { $yaml += " - api-key: `"$ApiKey`"" $yaml += ' models:' foreach ($gm in $gptModels) { + # Plain alias for the command line, prefixed alias so the /model + # picker's discovery filter (claude|anthropic substring) shows it. $yaml += " - name: `"$gm`"" $yaml += " alias: `"$gm`"" + $yaml += " - name: `"$gm`"" + $yaml += " alias: `"anthropic-compat/$gm`"" } Write-RawText (Join-Path $HomeDir 'proxy-config.yaml') (($yaml -join "`n") + "`n") New-Item -ItemType Directory -Force -Path (Join-Path $HomeDir 'proxy-auth') | Out-Null diff --git a/install.sh b/install.sh index 3819931..ac13858 100755 --- a/install.sh +++ b/install.sh @@ -443,17 +443,38 @@ ok "statusline installed to $HOME_DIR/statusline.sh" # Non-fatal throughout: Claude models work without any of this. If a step # fails, the install continues and `qbraid-code --model gpt-*` explains itself. say "GPT models" + +# The /model picker integration needs `claude-code.disable-cloaking-model-list` +# (CLIProxyAPI >= 7.2.135-ish); an older binary would show the picker reversed +# pseudo-model gibberish. Feature-detect on the binary itself, not a version +# number. +proxy_supports_picker() { + # grep -q exits at the first match, strings then dies of SIGPIPE, and + # pipefail would turn that into failure-on-success. Same class of bug as + # the json extractors; same cure. + ( + set +o pipefail + strings "$1" 2>/dev/null | grep -q "disable-cloaking-model-list" + ) +} + PROXY_BIN="" -if command -v cliproxyapi >/dev/null 2>&1; then - PROXY_BIN="$(command -v cliproxyapi)" - ok "using existing $PROXY_BIN" -elif [ -x "$HOME_DIR/cliproxyapi" ]; then - PROXY_BIN="$HOME_DIR/cliproxyapi" - ok "using existing $PROXY_BIN" -elif [ "$OS" = darwin ] && command -v brew >/dev/null 2>&1; then - if brew install cliproxyapi >/dev/null 2>&1; then - PROXY_BIN="$(command -v cliproxyapi)" - ok "proxy installed via Homebrew" +for cand in "$(command -v cliproxyapi 2>/dev/null || true)" "$HOME_DIR/cliproxyapi"; do + [ -n "$cand" ] && [ -x "$cand" ] || continue + if proxy_supports_picker "$cand"; then + PROXY_BIN="$cand" + ok "using existing $PROXY_BIN" + break + fi + warn "$cand is too old for the /model picker — upgrading" +done +if [ -z "$PROXY_BIN" ] && [ "$OS" = darwin ] && command -v brew >/dev/null 2>&1; then + if brew install cliproxyapi >/dev/null 2>&1 || brew upgrade cliproxyapi >/dev/null 2>&1; then + CAND="$(command -v cliproxyapi 2>/dev/null || true)" + if [ -n "$CAND" ] && proxy_supports_picker "$CAND"; then + PROXY_BIN="$CAND" + ok "proxy installed via Homebrew" + fi fi fi if [ -z "$PROXY_BIN" ]; then @@ -465,7 +486,8 @@ if [ -z "$PROXY_BIN" ]; then PROXY_URL="https://github.com/$PROXY_REPO/releases/download/$TAG/CLIProxyAPI_${VER}_${OS}_${PROXY_ARCH}.tar.gz" PROXY_TMP=$(mktemp -d) if curl -fsSL -m 120 -o "$PROXY_TMP/cpa.tar.gz" "$PROXY_URL" 2>/dev/null \ - && tar xzf "$PROXY_TMP/cpa.tar.gz" -C "$PROXY_TMP" cli-proxy-api 2>/dev/null; then + && tar xzf "$PROXY_TMP/cpa.tar.gz" -C "$PROXY_TMP" cli-proxy-api 2>/dev/null \ + && proxy_supports_picker "$PROXY_TMP/cli-proxy-api"; then install -m 0755 "$PROXY_TMP/cli-proxy-api" "$HOME_DIR/cliproxyapi" PROXY_BIN="$HOME_DIR/cliproxyapi" ok "proxy installed to $PROXY_BIN" @@ -510,6 +532,11 @@ remote-management: allow-remote: false disable-control-panel: true debug: false +# Model-list cloaking rewrites ids into reversed pseudo-claude names so they +# pass Claude Code's discovery filter — the picker then shows gibberish. Our +# anthropic-compat/ aliases pass the filter with readable names instead. +claude-code: + disable-cloaking-model-list: true claude-api-key: - api-key: "$API_KEY" base-url: "$GATEWAY_URL" @@ -529,7 +556,13 @@ openai-compatibility: PEOF printf '%s\n' "$GPT_MODELS" | while IFS= read -r gm; do [ -n "$gm" ] || continue + # Two aliases per GPT model: the plain id for the command line, and a + # prefixed one containing "anthropic" so Claude Code's /model picker + # discovery filter (which keeps only ids containing claude|anthropic) + # shows it. The alias IS the inference mapping — selecting the + # prefixed row routes to the same upstream model. printf ' - name: "%s"\n alias: "%s"\n' "$gm" "$gm" + printf ' - name: "%s"\n alias: "anthropic-compat/%s"\n' "$gm" "$gm" done } > "$HOME_DIR/proxy-config.yaml" chmod 600 "$HOME_DIR/proxy-config.yaml" diff --git a/qbraid-code b/qbraid-code index 1ecedff..a8cc8d7 100755 --- a/qbraid-code +++ b/qbraid-code @@ -204,15 +204,19 @@ if [ -n "$PROXY_BIN" ] && [ -x "$PROXY_BIN" ] && [ -s "$HOME_DIR/proxy-config.ya if start_proxy; then RUN_BASE="$PROXY_URL" RUN_TOKEN=$(cat "$HOME_DIR/proxy.key") + # Populate the /model picker from the proxy's model list. Claude Code + # fetches {base}/v1/models at startup and shows rows labeled "From + # gateway"; GPT models appear via their anthropic-compat/ aliases. + export CLAUDE_CODE_ENABLE_GATEWAY_MODEL_DISCOVERY=1 else case "$RUN_MODEL" in - gpt-*) exit 1 ;; # start_proxy already explained + gpt-*|*/gpt-*) exit 1 ;; # start_proxy already explained *) echo "qbraid-code: proxy unavailable — using the gateway directly (Claude models only)." >&2 ;; esac fi else case "$RUN_MODEL" in - gpt-*) + gpt-*|*/gpt-*) echo "qbraid-code: GPT models need the local proxy, which is not installed." >&2 echo "Re-run the installer: curl -fsSL https://qbraid.com/code.sh | bash" >&2 exit 1 ;; diff --git a/qbraid-code.cmd b/qbraid-code.cmd index 69785fd..22e9111 100644 --- a/qbraid-code.cmd +++ b/qbraid-code.cmd @@ -63,6 +63,8 @@ powershell -NoProfile -ExecutionPolicy Bypass -File "%QC_HOME%\qbraid-proxy.ps1" if errorlevel 1 goto :noproxy set /p RUNTOKEN=<"%QC_HOME%\proxy.key" set "RUNBASE=http://127.0.0.1:8320" +rem Populate the /model picker from the proxy's model list at startup. +set "CLAUDE_CODE_ENABLE_GATEWAY_MODEL_DISCOVERY=1" goto :routed :noproxy if /i "%RUNMODEL:~0,4%"=="gpt-" ( diff --git a/tests/extractors.sh b/tests/extractors.sh index b6719f0..eec8165 100755 --- a/tests/extractors.sh +++ b/tests/extractors.sh @@ -52,5 +52,20 @@ check "json_num still extracts a present number" \ check "json_str on an empty body survives set -e" \ "x=\$(json_str '' name); [ -z \"\$x\" ]" +# proxy_supports_picker: grep -q + pipefail must not fail on success. +FN2=$(awk '/^proxy_supports_picker\(\) \{/{f=1} f{print} f&&/^\}$/{exit}' install.sh) +if [ -n "$FN2" ] && bash -c "set -euo pipefail; $FN2; proxy_supports_picker /bin/ls || true; exit 0"; then + # positive case: a file that certainly contains the marker + tmpbin=$(mktemp); printf 'xx disable-cloaking-model-list yy' > "$tmpbin" + if bash -c "set -euo pipefail; $FN2; proxy_supports_picker '$tmpbin'"; then + pass=$((pass + 1)); printf ' ok proxy_supports_picker survives pipefail\n' + else + fail=$((fail + 1)); printf ' FAIL proxy_supports_picker false-negative under pipefail\n' + fi + rm -f "$tmpbin" +else + fail=$((fail + 1)); printf ' FAIL proxy_supports_picker extraction or negative case\n' +fi + printf '\n%d passed, %d failed\n' "$pass" "$fail" [ "$fail" -eq 0 ]