#!/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" # Local example: 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