Spaces:
Runtime error
Runtime error
Ashira Pitchayapakayakul commited on
Commit Β·
cbaca91
1
Parent(s): b4668b2
feat(bridges): add openrouter + gemini bridges (v2 ladder needed them)
Browse filesRound 5/6 v2 scripts reference these but the bridges themselves were never
shipped. Now matches existing cerebras/groq/chutes interface (text-prompt
input, env-key auth via ~/.hermes/.env).
- bin/openrouter-bridge.sh: meta-router, free models (llama-3.3-70b/
deepseek-r1/qwen-coder/gemini-flash), pool-key support + auto-fallback
through OPENROUTER_API_KEY/_2/_3 on 401/403/429.
- bin/gemini-bridge.sh: Google AI Studio free tier (gemini-2.5-flash /
2.5-pro / 2.0-flash-exp / 2.5-flash-lite), accepts GEMINI_API_KEY or
GOOGLE_API_KEY.
Trigger: Space rebuild on push to wake stuck status-server.
- bin/gemini-bridge.sh +75 -0
- bin/openrouter-bridge.sh +95 -0
bin/gemini-bridge.sh
ADDED
|
@@ -0,0 +1,75 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
#!/usr/bin/env bash
|
| 2 |
+
# Gemini bridge β Google AI Studio free tier (15 RPM, 1M tokens/day on flash).
|
| 3 |
+
# Models: gemini-2.5-pro (paid), gemini-2.5-flash (free), gemini-2.0-flash-exp (free).
|
| 4 |
+
#
|
| 5 |
+
# Usage:
|
| 6 |
+
# echo "<prompt>" | gemini-bridge.sh [--model fast|pro] [--max-tokens N]
|
| 7 |
+
# gemini-bridge.sh "prompt as arg"
|
| 8 |
+
set -u
|
| 9 |
+
MODEL="gemini-2.5-flash"
|
| 10 |
+
MAX_TOKENS=2000
|
| 11 |
+
TEMP=0.3
|
| 12 |
+
PROMPT=""
|
| 13 |
+
|
| 14 |
+
while [[ $# -gt 0 ]]; do
|
| 15 |
+
case "$1" in
|
| 16 |
+
--model)
|
| 17 |
+
case "$2" in
|
| 18 |
+
fast|small) MODEL="gemini-2.5-flash" ;;
|
| 19 |
+
pro|big) MODEL="gemini-2.5-pro" ;;
|
| 20 |
+
exp) MODEL="gemini-2.0-flash-exp" ;;
|
| 21 |
+
lite) MODEL="gemini-2.5-flash-lite" ;;
|
| 22 |
+
*) MODEL="$2" ;;
|
| 23 |
+
esac; shift 2 ;;
|
| 24 |
+
--max-tokens) MAX_TOKENS="$2"; shift 2 ;;
|
| 25 |
+
--temperature) TEMP="$2"; shift 2 ;;
|
| 26 |
+
*) PROMPT="$*"; break ;;
|
| 27 |
+
esac
|
| 28 |
+
done
|
| 29 |
+
[[ -z "$PROMPT" ]] && [[ ! -t 0 ]] && PROMPT=$(cat)
|
| 30 |
+
[[ -z "$PROMPT" ]] && { echo "gemini-bridge: no prompt" >&2; exit 2; }
|
| 31 |
+
|
| 32 |
+
LOG="$HOME/.surrogate/logs/gemini-bridge.log"
|
| 33 |
+
mkdir -p "$(dirname "$LOG")"
|
| 34 |
+
[[ -f "$HOME/.hermes/.env" ]] && { set -a; source "$HOME/.hermes/.env"; set +a; }
|
| 35 |
+
echo "[$(date '+%H:%M:%S')] model=$MODEL len=${#PROMPT}" >> "$LOG"
|
| 36 |
+
|
| 37 |
+
KEY="${GEMINI_API_KEY:-${GOOGLE_API_KEY:-}}"
|
| 38 |
+
RESPONSE=$(MODEL="$MODEL" MAX_TOKENS="$MAX_TOKENS" TEMP="$TEMP" KEY="$KEY" \
|
| 39 |
+
python3 -c "
|
| 40 |
+
import json, os, sys, urllib.request, urllib.error
|
| 41 |
+
key = os.environ.get('KEY','')
|
| 42 |
+
if not key:
|
| 43 |
+
print('gemini-bridge: no GEMINI_API_KEY/GOOGLE_API_KEY', file=sys.stderr); sys.exit(2)
|
| 44 |
+
model = os.environ['MODEL']
|
| 45 |
+
body = {
|
| 46 |
+
'contents': [{'parts':[{'text': sys.stdin.read()}]}],
|
| 47 |
+
'generationConfig': {
|
| 48 |
+
'maxOutputTokens': int(os.environ['MAX_TOKENS']),
|
| 49 |
+
'temperature': float(os.environ['TEMP']),
|
| 50 |
+
},
|
| 51 |
+
}
|
| 52 |
+
url = f'https://generativelanguage.googleapis.com/v1beta/models/{model}:generateContent?key={key}'
|
| 53 |
+
req = urllib.request.Request(url,
|
| 54 |
+
data=json.dumps(body).encode(),
|
| 55 |
+
headers={'Content-Type':'application/json'})
|
| 56 |
+
try:
|
| 57 |
+
with urllib.request.urlopen(req, timeout=120) as r:
|
| 58 |
+
d = json.load(r)
|
| 59 |
+
cand = d.get('candidates',[{}])[0]
|
| 60 |
+
parts = cand.get('content',{}).get('parts',[])
|
| 61 |
+
text = ''.join(p.get('text','') for p in parts)
|
| 62 |
+
if not text and cand.get('finishReason'):
|
| 63 |
+
print(f'gemini-bridge: finish_reason={cand[\"finishReason\"]}', file=sys.stderr); sys.exit(1)
|
| 64 |
+
print(text)
|
| 65 |
+
except urllib.error.HTTPError as e:
|
| 66 |
+
msg = e.read().decode('utf-8','ignore')[:400]
|
| 67 |
+
print(f'gemini-bridge HTTP {e.code}: {msg}', file=sys.stderr)
|
| 68 |
+
sys.exit(1)
|
| 69 |
+
except Exception as e:
|
| 70 |
+
print(f'gemini-bridge error: {e}', file=sys.stderr); sys.exit(1)
|
| 71 |
+
" <<< "$PROMPT")
|
| 72 |
+
RC=$?
|
| 73 |
+
echo "[$(date '+%H:%M:%S')] rc=$RC bytes=${#RESPONSE}" >> "$LOG"
|
| 74 |
+
[[ $RC -ne 0 ]] && exit $RC
|
| 75 |
+
echo "$RESPONSE"
|
bin/openrouter-bridge.sh
ADDED
|
@@ -0,0 +1,95 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
#!/usr/bin/env bash
|
| 2 |
+
# OpenRouter bridge β meta-router across many providers.
|
| 3 |
+
# Free models: qwen/qwen-2.5-coder-32b-instruct:free, deepseek/deepseek-r1:free,
|
| 4 |
+
# meta-llama/llama-3.3-70b-instruct:free, google/gemini-2.0-flash-exp:free.
|
| 5 |
+
#
|
| 6 |
+
# Usage (matches cerebras/groq/chutes interface):
|
| 7 |
+
# echo "<prompt>" | openrouter-bridge.sh [--model fast|big|free|<id>] [--max-tokens N]
|
| 8 |
+
# openrouter-bridge.sh "prompt as arg"
|
| 9 |
+
set -u
|
| 10 |
+
MODEL="meta-llama/llama-3.3-70b-instruct:free"
|
| 11 |
+
MAX_TOKENS=2000
|
| 12 |
+
TEMP=0.3
|
| 13 |
+
PROMPT=""
|
| 14 |
+
|
| 15 |
+
while [[ $# -gt 0 ]]; do
|
| 16 |
+
case "$1" in
|
| 17 |
+
--model)
|
| 18 |
+
case "$2" in
|
| 19 |
+
fast|small) MODEL="meta-llama/llama-3.3-70b-instruct:free" ;;
|
| 20 |
+
big) MODEL="deepseek/deepseek-r1:free" ;;
|
| 21 |
+
code|coder) MODEL="meta-llama/llama-3.3-70b-instruct:free" ;;
|
| 22 |
+
gemini) MODEL="google/gemini-2.0-flash-exp:free" ;;
|
| 23 |
+
free) MODEL="meta-llama/llama-3.3-70b-instruct:free" ;;
|
| 24 |
+
*) MODEL="$2" ;;
|
| 25 |
+
esac; shift 2 ;;
|
| 26 |
+
--max-tokens) MAX_TOKENS="$2"; shift 2 ;;
|
| 27 |
+
--temperature) TEMP="$2"; shift 2 ;;
|
| 28 |
+
*) PROMPT="$*"; break ;;
|
| 29 |
+
esac
|
| 30 |
+
done
|
| 31 |
+
[[ -z "$PROMPT" ]] && [[ ! -t 0 ]] && PROMPT=$(cat)
|
| 32 |
+
[[ -z "$PROMPT" ]] && { echo "openrouter-bridge: no prompt" >&2; exit 2; }
|
| 33 |
+
|
| 34 |
+
LOG="$HOME/.surrogate/logs/openrouter-bridge.log"
|
| 35 |
+
mkdir -p "$(dirname "$LOG")"
|
| 36 |
+
[[ -f "$HOME/.hermes/.env" ]] && { set -a; source "$HOME/.hermes/.env"; set +a; }
|
| 37 |
+
echo "[$(date '+%H:%M:%S')] model=$MODEL len=${#PROMPT}" >> "$LOG"
|
| 38 |
+
|
| 39 |
+
# Pool support: if OPENROUTER_POOL is set (csv of keys), pick one round-robin.
|
| 40 |
+
# Else try OPENROUTER_API_KEY β OPENROUTER_API_KEY_2 β OPENROUTER_API_KEY_3.
|
| 41 |
+
if [[ -n "${OPENROUTER_POOL:-}" ]]; then
|
| 42 |
+
IFS=',' read -ra _KEYS <<< "$OPENROUTER_POOL"
|
| 43 |
+
_N=${#_KEYS[@]}
|
| 44 |
+
_IDX=$(( ($(date +%s) / 30) % _N ))
|
| 45 |
+
OPENROUTER_API_KEY="${_KEYS[$_IDX]}"
|
| 46 |
+
fi
|
| 47 |
+
# Auto-fallback: if primary 401s, the python below retries with _2 then _3
|
| 48 |
+
OR_KEYS=""
|
| 49 |
+
for k in OPENROUTER_API_KEY OPENROUTER_API_KEY_2 OPENROUTER_API_KEY_3; do
|
| 50 |
+
v="${!k:-}"
|
| 51 |
+
[[ -n "$v" ]] && OR_KEYS="${OR_KEYS}${OR_KEYS:+,}${v}"
|
| 52 |
+
done
|
| 53 |
+
|
| 54 |
+
RESPONSE=$(MODEL="$MODEL" MAX_TOKENS="$MAX_TOKENS" TEMP="$TEMP" OR_KEYS="$OR_KEYS" \
|
| 55 |
+
python3 -c "
|
| 56 |
+
import json, os, sys, urllib.request, urllib.error
|
| 57 |
+
keys = [k for k in os.environ.get('OR_KEYS','').split(',') if k]
|
| 58 |
+
if not keys:
|
| 59 |
+
print('openrouter-bridge: no OPENROUTER_API_KEY*', file=sys.stderr); sys.exit(2)
|
| 60 |
+
body = {
|
| 61 |
+
'model': os.environ['MODEL'],
|
| 62 |
+
'messages': [{'role':'user','content': sys.stdin.read()}],
|
| 63 |
+
'max_tokens': int(os.environ['MAX_TOKENS']),
|
| 64 |
+
'temperature': float(os.environ['TEMP']),
|
| 65 |
+
}
|
| 66 |
+
data = json.dumps(body).encode()
|
| 67 |
+
last_err = ''
|
| 68 |
+
for key in keys:
|
| 69 |
+
req = urllib.request.Request(
|
| 70 |
+
'https://openrouter.ai/api/v1/chat/completions',
|
| 71 |
+
data=data,
|
| 72 |
+
headers={
|
| 73 |
+
'Content-Type':'application/json',
|
| 74 |
+
'Authorization':'Bearer '+key,
|
| 75 |
+
'HTTP-Referer':'https://axentx.dev/surrogate-1',
|
| 76 |
+
'X-Title':'Surrogate-1',
|
| 77 |
+
})
|
| 78 |
+
try:
|
| 79 |
+
with urllib.request.urlopen(req, timeout=120) as r:
|
| 80 |
+
d = json.load(r)
|
| 81 |
+
print(d.get('choices',[{}])[0].get('message',{}).get('content',''))
|
| 82 |
+
sys.exit(0)
|
| 83 |
+
except urllib.error.HTTPError as e:
|
| 84 |
+
last_err = f'HTTP {e.code}: {e.read().decode(\"utf-8\",\"ignore\")[:300]}'
|
| 85 |
+
if e.code in (401, 403, 429):
|
| 86 |
+
continue # try next key
|
| 87 |
+
break
|
| 88 |
+
except Exception as e:
|
| 89 |
+
last_err = str(e); break
|
| 90 |
+
print(f'openrouter-bridge {last_err}', file=sys.stderr); sys.exit(1)
|
| 91 |
+
" <<< "$PROMPT")
|
| 92 |
+
RC=$?
|
| 93 |
+
echo "[$(date '+%H:%M:%S')] rc=$RC bytes=${#RESPONSE}" >> "$LOG"
|
| 94 |
+
[[ $RC -ne 0 ]] && exit $RC
|
| 95 |
+
echo "$RESPONSE"
|