#!/usr/bin/env bash # Call one model via provider protocol. # Protocols: openai | openai-completions | openai-responses | anthropic # (openai / openai-compatible → chat completions) # Args: set -euo pipefail PROVIDER="${1:?provider}" MODEL="${2:?model}" PROTOCOL="${3:?protocol}" BASE_URL="${4:?base_url}" PROMPT_FILE="${5:?prompt}" OUT_FILE="${6:?out}" SECRETS_FILE="${7:?secrets}" KEY=$(python3 -c 'import json,sys; print(json.load(open(sys.argv[1])).get(sys.argv[2],""))' "$SECRETS_FILE" "$PROVIDER") if [[ -z "$KEY" ]]; then echo "missing key for $PROVIDER" >&2 echo "{\"error\":\"missing_key\",\"model\":\"$MODEL\",\"provider\":\"$PROVIDER\"}" > "$OUT_FILE" exit 0 fi MAX_TOKENS="${GATEKEEP_MAX_TOKENS:-4096}" SYSTEM=$(python3 -c 'import json,sys; print(json.load(open(sys.argv[1]))["system"])' "$PROMPT_FILE") USER=$(python3 -c 'import json,sys; print(json.load(open(sys.argv[1]))["user"])' "$PROMPT_FILE") LABEL="${PROVIDER}:${MODEL}" # Normalize protocol aliases WIRE=$(python3 -c 'import sys p=sys.argv[1].lower() m={"openai":"openai-completions","openai-compatible":"openai-completions", "openai-completions":"openai-completions","openai-responses":"openai-responses", "anthropic":"anthropic"} print(m.get(p,""))' "$PROTOCOL") if [[ -z "$WIRE" ]]; then echo "{\"error\":\"bad_protocol\",\"protocol\":\"$PROTOCOL\",\"model\":\"$MODEL\"}" > "$OUT_FILE" exit 0 fi if [[ "$WIRE" == "openai-completions" ]]; then BODY=$(python3 - "$MODEL" "$SYSTEM" "$USER" "$MAX_TOKENS" <<'PY' import json,sys model, system, user, max_tokens = sys.argv[1], sys.argv[2], sys.argv[3], int(sys.argv[4]) print(json.dumps({ "model": model, "temperature": 0, "max_tokens": max_tokens, "messages": [ {"role": "system", "content": system}, {"role": "user", "content": user}, ], })) PY ) HTTP_CODE=$(curl -sS -o "$OUT_FILE.raw" -w "%{http_code}" \ "${BASE_URL%/}/chat/completions" \ -H "Authorization: Bearer $KEY" \ -H "Content-Type: application/json" \ -H "HTTP-Referer: https://github.com/${GITHUB_REPOSITORY:-gatekeep}" \ -H "X-Title: Gatekeep" \ -d "$BODY") EXTRACT=openai elif [[ "$WIRE" == "openai-responses" ]]; then BODY=$(python3 - "$MODEL" "$SYSTEM" "$USER" "$MAX_TOKENS" <<'PY' import json,sys model, system, user, max_tokens = sys.argv[1], sys.argv[2], sys.argv[3], int(sys.argv[4]) print(json.dumps({ "model": model, "temperature": 0, "max_output_tokens": max_tokens, "input": [ {"role": "system", "content": system}, {"role": "user", "content": user}, ], })) PY ) HTTP_CODE=$(curl -sS -o "$OUT_FILE.raw" -w "%{http_code}" \ "${BASE_URL%/}/responses" \ -H "Authorization: Bearer $KEY" \ -H "Content-Type: application/json" \ -d "$BODY") EXTRACT=openai-responses elif [[ "$WIRE" == "anthropic" ]]; then BODY=$(python3 - "$MODEL" "$SYSTEM" "$USER" "$MAX_TOKENS" <<'PY' import json,sys model, system, user, max_tokens = sys.argv[1], sys.argv[2], sys.argv[3], int(sys.argv[4]) print(json.dumps({ "model": model, "max_tokens": max_tokens, "temperature": 0, "system": system, "messages": [{"role": "user", "content": user}], })) PY ) HTTP_CODE=$(curl -sS -o "$OUT_FILE.raw" -w "%{http_code}" \ "${BASE_URL%/}/v1/messages" \ -H "x-api-key: $KEY" \ -H "anthropic-version: 2023-06-01" \ -H "Content-Type: application/json" \ -d "$BODY") EXTRACT=anthropic fi if [[ "$HTTP_CODE" != "200" ]]; then echo "$LABEL failed HTTP $HTTP_CODE" >&2 head -c 800 "$OUT_FILE.raw" >&2 || true echo "{\"error\":\"http\",\"model\":\"$MODEL\",\"provider\":\"$PROVIDER\",\"http\":$HTTP_CODE}" > "$OUT_FILE" exit 0 fi python3 - "$OUT_FILE.raw" "$OUT_FILE" "$MODEL" "$PROVIDER" "$EXTRACT" <<'PY' import json,sys raw=json.load(open(sys.argv[1])) out, model, provider, kind = sys.argv[2], sys.argv[3], sys.argv[4], sys.argv[5] if kind == "anthropic": text = "".join( (c.get("text") or "") for c in (raw.get("content") or []) if c.get("type") == "text" ) elif kind == "openai-responses": # Best-effort extract from Responses API text = raw.get("output_text") or "" if not text: chunks = [] for item in raw.get("output") or []: for c in item.get("content") or []: if c.get("type") in ("output_text", "text"): chunks.append(c.get("text") or "") text = "".join(chunks) else: text = (((raw.get("choices") or [{}])[0].get("message") or {}).get("content")) or "" start, end = text.find("{"), text.rfind("}") payload = {} if start >= 0 and end > start: try: payload = json.loads(text[start:end+1]) except Exception as e: payload = {"parse_error": str(e), "raw_text": text[:2000]} else: payload = {"parse_error": "no_json", "raw_text": text[:2000]} payload["_model"] = f"{provider}:{model}" payload["_provider"] = provider json.dump(payload, open(out,"w"), indent=2) PY echo "$LABEL → $OUT_FILE" >&2