Files
dynagate/free-model-collector.sh
T
2026-09-14 13:36:46 +03:00

138 lines
6.0 KiB
Bash
Executable File

#!/bin/sh
# Free model collector script for Dynagate
# Depends upon curl and jq
# Created by Luxferre in 2026, released into the public domain
REQ='curl -fsSL -4'
CONFIG='models.csv'
TCONFIG="$(mktemp)"
# Write OpenCode Zen free models
$REQ https://opencode.ai/zen/v1/models | jq -r '.data[] | select(.id | test("-free")) | "\(.id),-,https://opencode.ai/zen/v1,{\"User-Agent\":\"opencode/1.18.30\"}"' >> "$TCONFIG"
# Write Big Pickle as hardcode
echo 'big-pickle,-,https://opencode.ai/zen/v1,{"User-Agent":"opencode/1.18.30"}'>> "$TCONFIG"
# Write Kilo free models
$REQ https://api.kilo.ai/api/gateway/models | jq -r '.data[] | select(.isFree) | "\(.id),-,https://api.kilo.ai/api/gateway,"' >> "$TCONFIG"
# Write OVHCloud free trial models (2 req per minute per IP keyless), commented out by default
# $REQ https://oai.endpoints.kepler.ai.cloud.ovh.net/v1/models | jq -r '.data[] | "\(.id),-,https://oai.endpoints.kepler.ai.cloud.ovh.net,"' | grep -Ev 'tts-|whisper-|bge-' >> "$TCONFIG"
# Write LLM7 API models, heavily rate-limited
$REQ https://api.llm7.io/v1/models | jq -r '.data[] | select(.usage_based_only | not) | "\(.id),-,https://api.llm7.io/v1,"' >> "$TCONFIG"
# Write UncloseAI models
$REQ https://hermes.ai.unturf.com/v1/models | jq -r '.data[] | "\(.id),-,https://hermes.ai.unturf.com/v1,"' >> "$TCONFIG"
# Write AIHorde models, slow but community-powered, commented out by default
# $REQ https://oai.aihorde.net/v1/models | jq -r '.data[] | "\(.id),0000000000,https://oai.aihorde.net/v1,"' >> "$TCONFIG"
# Write Pekpik models (need to be rotated frequently, highly experimental, commented out by default)
# $REQ https://aiapiv2.pekpik.com/free-keys.json | jq -r '.keys[] | "\(.model),\(.key),https://aiapiv2.pekpik.com/v1,"' >> "$TCONFIG"
## Write some independent gateways found on HF
$REQ https://mmoranguito-openai-nim-proxy.hf.space/v1/models | jq -r '.data[] | "\(.id),-,https://mmoranguito-openai-nim-proxy.hf.space/v1,"' >> "$TCONFIG"
$REQ https://garystu-openai-nim-proxy.hf.space/v1/models | jq -r '.data[] | "\(.id),-,https://garystu-openai-nim-proxy.hf.space/v1,"' >> "$TCONFIG"
$REQ https://zsnonsky-nim-proxy.hf.space/v1/models | jq -r '.data[] | "\(.id),-,https://zsnonsky-nim-proxy.hf.space/v1,"' >> "$TCONFIG"
# this one works but is very slow, commented out by default
# $REQ https://covariate-my-nvidia-proxy.hf.space/v1/models | jq -r '.data[] | "\(.id),-,https://covariate-my-nvidia-proxy.hf.space/v1,"' >> "$TCONFIG"
# Write XORTRON as hardcode
echo 'xortron,-,https://darkc0de-chat.hf.space/v1,'>> "$TCONFIG"
# Write hygate as hardcode
echo 'hy3,-,http://127.0.0.1:9875/v1,'>> "$TCONFIG"
# Verify each collected model for responsiveness under the actual /chat/completions
# (prompt it to respond with OK). Unresponsive models are dropped from the final file.
# For non-timeout drops, the HTTP response code is reported for diagnostics.
VCONFIG="$(mktemp)"
SKIPF="$(mktemp)"
# OpenCode Zen endpoints require an X-Session-ID header on chat requests
sid="$(uuidgen 2>/dev/null || cat /proc/sys/kernel/random/uuid 2>/dev/null || python3 -c 'import uuid;print(uuid.uuid4())' 2>/dev/null)"
[ -z "$sid" ] && sid="$(head -c 16 /dev/urandom | od -An -tx1 | tr -d ' ')"
# Quote an arbitrary value as a single CSV field (RFC 4180). The extra
# column carries a JSON header map such as {"User-Agent":"opencode/1.18.30"};
# its bare double quotes would otherwise abort the Go encoding/csv reader
# on configuration reload.
csv_quote() {
printf '"%s"' "$(printf '%s' "$1" | sed 's/"/""/g')"
}
while IFS=',' read -r model key endpoint extra; do
[ -z "$model" ] && continue
case "$model" in *embed*) echo "Skipped (embedding model): $model @ $endpoint" >&2; continue;; esac
if grep -qxF "$endpoint" "$SKIPF" 2>/dev/null; then
echo "Skipped (endpoint unreachable): $model @ $endpoint" >&2
continue
fi
payload="$(jq -nc --arg m "$model" '{model:$m,messages:[{role:"user",content:"Reply with the word OK only."}],max_tokens:8,stream:true}')"
bodyf="$(mktemp)"
# Build curl args as a single-quoted list so each "-H Name: value" stays ONE
# argument. Unquoted variable expansion would word-split "Name: value" and turn
# the value into a stray URL, causing spurious "curl error 6" (couldn't resolve).
set -- -sS -4 -m 8 -o "$bodyf" -w '%{http_code}|%{time_starttransfer}' -X POST "$endpoint/chat/completions" -H 'Content-Type: application/json'
[ "$key" != '-' ] && set -- "$@" -H "Authorization: Bearer $key"
case "$endpoint" in *opencode.ai/zen*) set -- "$@" -H "X-Session-ID: $sid" -H "User-Agent: opencode/1.18.30";; esac
set -- "$@" -d "$payload"
attempt=0
max_attempts=3
ok=0
reason=''
while [ "$attempt" -lt "$max_attempts" ]; do
attempt=$((attempt+1))
out="$(curl "$@" 2>/dev/null)"
rc=$?
http_code="${out%%|*}"
ttft="${out##*|}"
if [ "$http_code" -ge 200 ] && [ "$http_code" -lt 300 ] && awk "BEGIN{exit !($ttft>0)}"; then
ok=1
break
fi
if [ "$rc" -eq 28 ]; then
reason="Timeout error (no first token within 8s)"
elif [ "$rc" -ne 0 ]; then
reason="curl error $rc"
else
reason="HTTP $http_code"
fi
retryable=0
if [ "$rc" -eq 28 ] || [ "$rc" -ne 0 ] || [ "$http_code" -eq 429 ] || { [ "$http_code" -ge 500 ] && [ "$http_code" -lt 600 ]; }; then
retryable=1
fi
if [ "$retryable" -eq 1 ] && [ "$attempt" -lt "$max_attempts" ]; then
sleep 3
else
break
fi
done
if [ "$ok" -eq 1 ]; then
echo "Added: $model @ $endpoint (TTFT: ${ttft}s)"
if [ -n "$extra" ]; then
printf '%s,%s,%s,%s\n' "$model" "$key" "$endpoint" "$(csv_quote "$extra")" >> "$VCONFIG"
else
printf '%s,%s,%s,%s\n' "$model" "$key" "$endpoint" "" >> "$VCONFIG"
fi
else
echo "Unresponsive ($reason), dropped: $model @ $endpoint" >&2
if [ "$rc" -eq 6 ] || [ "$rc" -eq 7 ]; then
printf '%s\n' "$endpoint" >> "$SKIPF"
fi
fi
rm -f "$bodyf"
done < "$TCONFIG"
rm -f "$TCONFIG"
rm -f "$SKIPF"
# Write the config header
printf 'model,key,endpoint,extra\n' > "$CONFIG"
# Finalize the config
sort -u "$VCONFIG" >> "$CONFIG"
rm -f "$VCONFIG"
exit 0