Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -59,8 +59,8 @@ OpenAI-compatible surface. Because it is one endpoint, `/model <name>` works
for **any** of the models mid-session, e.g. `/model gpt-5.6-sol`.

If the proxy is missing, Claude models automatically fall back to the gateway
directly — they can never break because of it. `qbraid-code --stop` shuts the
proxy down.
directly — they can never break because of it. It shuts down by itself when
you quit the session that started it; `qbraid-code --stop` stops it by hand.

One caveat: GPT models accept at most 128 tools, so with many MCP servers add
`--strict-mcp-config`.
Expand Down
44 changes: 28 additions & 16 deletions install.sh
Original file line number Diff line number Diff line change
Expand Up @@ -300,36 +300,48 @@ else
CREDITS="unknown"
fi

# /organizations/current returns the organization document, so `name` is the
# organization's. /organizations/me returns MEMBERSHIP, whose first `name` is
# the USER's — labelling the confirmation with that would be worse than
# showing nothing. Scope the match to the data object, and print the id
# alongside so a bad parse cannot quietly point someone at the wrong org.
# An API key cannot read its organization's NAME: /organizations/current needs
# a JWT org context and /organizations rejects key auth outright (both verified
# 2026-08-20). Showing a raw Mongo id and asking "is this right?" is worse than
# not asking — nobody recognises 507f1f77bcf86cd799439011. Show what the key
# DOES tell us (plan, credits), name the organization only when it resolves,
# and make the escape hatch the actionable sentence.
ORG_NAME=""
if [ -n "$ORG_ID" ]; then
api_get "$API_BASE/organizations/current" "$API_KEY" "$ORG_ID"
ORG_DATA=$(printf '%s' "$API_BODY" | sed 's/.*"data"[[:space:]]*:[[:space:]]*{//')
ORG_NAME=$(json_str "$ORG_DATA" name)
if [ "$HTTP_STATUS" = 200 ]; then
ORG_DATA=$(printf '%s' "$API_BODY" | sed 's/.*"data"[[:space:]]*:[[:space:]]*{//')
ORG_NAME=$(json_str "$ORG_DATA" name)
fi
fi

PLAN=""
api_get "$GATEWAY_URL/quota" "$API_KEY"
[ "$HTTP_STATUS" = 200 ] && PLAN=$(json_str "$API_BODY" plan)

printf '\n'
if [ -n "$ORG_NAME" ]; then
printf '\n Organization: %s%s%s %s(%s)%s\n' \
"$bold" "$ORG_NAME" "$rst" "$dim" "$ORG_ID" "$rst"
else
printf '\n Organization: %s%s%s\n' "$bold" "${ORG_ID:-unknown}" "$rst"
printf ' Organization: %s%s%s\n' "$bold" "$ORG_NAME" "$rst"
fi
[ -n "$PLAN" ] && printf ' Plan: %s%s%s\n' "$bold" "$PLAN" "$rst"
printf ' Credits: %s%s%s\n' "$bold" "$CREDITS" "$rst"
if [ -z "$ORG_NAME" ]; then
printf '\n These are the credits this API key can spend.\n'
printf ' Using a different organization means creating a key under it at\n'
printf ' %s%s%s\n' "$bold" "$KEYS_URL" "$rst"
fi
printf ' Credits: %s%s%s\n\n' "$bold" "$CREDITS" "$rst"
printf '\n'

if ! confirm "Is this the right organization?" y; then
if ! confirm "Continue with this account?" y; then
cat <<EOF

Switch organization at ${bold}https://account.qbraid.com${rst}, or create a key
under the organization you want, then run this installer again.
No problem. Create a key under the organization you want at
${bold}$KEYS_URL${rst}, then run this installer again.

EOF
exit 1
fi
ok "organization confirmed"
ok "account confirmed"

if [ "$CREDITS" != unknown ] && awk -v c="$CREDITS_RAW" 'BEGIN { exit !(c <= 0) }'; then
warn "this organization has no credits left — requests will fail until it is topped up."
Expand Down
51 changes: 43 additions & 8 deletions qbraid-code
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,8 @@ proxy_listening() {
-H "Authorization: Bearer $(cat "$HOME_DIR/proxy.key")" 2>/dev/null
}

PROXY_STARTED_BY_US=0

start_proxy() {
proxy_listening && return 0
[ -n "$PROXY_BIN" ] && [ -x "$PROXY_BIN" ] || {
Expand All @@ -52,7 +54,10 @@ start_proxy() {
>> "$HOME_DIR/proxy.log" 2>&1 &
i=0
while [ "$i" -lt 40 ]; do
proxy_listening && return 0
if proxy_listening; then
PROXY_STARTED_BY_US=1
return 0
fi
sleep 0.3
i=$((i + 1))
done
Expand Down Expand Up @@ -244,10 +249,40 @@ fi
# FORCED, not defaulted: an inherited MAX_THINKING_TOKENS from the user's
# shell re-enables thinking and turns every request into that 400. Relax to
# Claude-only once the gateway accepts "adaptive".
MAX_THINKING_TOKENS=0 \
ANTHROPIC_BASE_URL="$RUN_BASE" \
ANTHROPIC_AUTH_TOKEN="$RUN_TOKEN" \
ANTHROPIC_MODEL="$RUN_MODEL" \
ANTHROPIC_SMALL_FAST_MODEL="$RUN_MODEL" \
CLAUDE_CODE_SUBAGENT_MODEL="$RUN_MODEL" \
exec claude "$@"
run_claude() {
MAX_THINKING_TOKENS=0 \
ANTHROPIC_BASE_URL="$RUN_BASE" \
ANTHROPIC_AUTH_TOKEN="$RUN_TOKEN" \
ANTHROPIC_MODEL="$RUN_MODEL" \
ANTHROPIC_SMALL_FAST_MODEL="$RUN_MODEL" \
CLAUDE_CODE_SUBAGENT_MODEL="$RUN_MODEL" \
claude "$@"
}

if [ "$PROXY_STARTED_BY_US" = 1 ]; then
# We started the proxy for this session, so we clean it up — leaving a
# background process running after the user quits is our bug, not theirs.
# A proxy that was ALREADY running belongs to someone else: leave it.
# No `exec` here, so this shell survives to do the cleanup; the trap also
# covers Ctrl+C and terminal close.
stop_our_proxy() {
pkill -f "$PROXY_BIN -config $HOME_DIR/proxy-config.yaml" 2>/dev/null || true
}
trap 'stop_our_proxy' EXIT INT TERM HUP
run_claude "$@"
STATUS=$?
stop_our_proxy
trap - EXIT INT TERM HUP
exit "$STATUS"
fi

exec_claude() {
MAX_THINKING_TOKENS=0 \
ANTHROPIC_BASE_URL="$RUN_BASE" \
ANTHROPIC_AUTH_TOKEN="$RUN_TOKEN" \
ANTHROPIC_MODEL="$RUN_MODEL" \
ANTHROPIC_SMALL_FAST_MODEL="$RUN_MODEL" \
CLAUDE_CODE_SUBAGENT_MODEL="$RUN_MODEL" \
exec claude "$@"
}
exec_claude "$@"
8 changes: 7 additions & 1 deletion qbraid-code.cmd
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,7 @@ rem Unified route: the proxy serves every model on one endpoint (Claude
rem passthrough, GPT translated). Fall back to the direct gateway for Claude
rem models when the proxy is unavailable; GPT models require it.
if not exist "%QC_HOME%\proxy-config.yaml" goto :noproxy
powershell -NoProfile -ExecutionPolicy Bypass -File "%QC_HOME%\qbraid-proxy.ps1" status | find "not running" >nul && set "QC_PROXY_STARTED=1"
powershell -NoProfile -ExecutionPolicy Bypass -File "%QC_HOME%\qbraid-proxy.ps1" ensure
if errorlevel 1 goto :noproxy
set /p RUNTOKEN=<"%QC_HOME%\proxy.key"
Expand Down Expand Up @@ -103,8 +104,13 @@ set "ANTHROPIC_MODEL=%RUNMODEL%"
set "ANTHROPIC_SMALL_FAST_MODEL=%RUNMODEL%"
set "CLAUDE_CODE_SUBAGENT_MODEL=%RUNMODEL%"

rem If we started the proxy for this session, stop it when claude exits —
rem leaving a background process behind after the user quits is our bug.
rem A proxy that was already running belongs to someone else: leave it.
claude %*
exit /b !ERRORLEVEL!
set "CLAUDE_RC=!ERRORLEVEL!"
if defined QC_PROXY_STARTED powershell -NoProfile -ExecutionPolicy Bypass -File "%QC_HOME%\qbraid-proxy.ps1" stop >nul 2>&1
exit /b !CLAUDE_RC!

:incomplete
echo qbraid-code: "%QC_HOME%\env" is incomplete. 1>&2
Expand Down
Loading