Ashira Pitchayapakayakul commited on
Commit
cbaca91
Β·
1 Parent(s): b4668b2

feat(bridges): add openrouter + gemini bridges (v2 ladder needed them)

Browse files

Round 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.

Files changed (2) hide show
  1. bin/gemini-bridge.sh +75 -0
  2. 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"