@@ -42,6 +42,18 @@ variable "mks_key_endpoint" {
4242 type = string
4343}
4444
45+ variable "freeapi_base_url" {
46+ default = " "
47+ description = " Base URL OpenAI-compatible para FreeAPI (ej. $TF_VAR_freeapi_base_url)."
48+ type = string
49+ }
50+
51+ variable "freeapi_key_endpoint" {
52+ default = " "
53+ description = " Endpoint para solicitar API keys de FreeAPI."
54+ type = string
55+ }
56+
4557
4658# ================================
4759# Parámetros visibles en Coder
@@ -155,6 +167,15 @@ data "coder_parameter" "p05_autoprovision_mks_key" {
155167 mutable = true
156168}
157169
170+ data "coder_parameter" "p05_autoprovision_freeapi_key" {
171+ name = " 05_autoprovision_freeapi_key"
172+ display_name = " [AI/FreeAPI] Provisionar API_KEY automáticamente"
173+ description = " Si hay FREEAPI_BASE_URL/FREEAPI_KEY_ENDPOINT, solicita una key y configura FreeAPI en OpenCode."
174+ type = " bool"
175+ default = true
176+ mutable = true
177+ }
178+
158179data "coder_parameter" "p05_opencode_provider_url" {
159180 name = " 05_opencode_provider_url"
160181 display_name = " [AI/OpenAI] Base URL (opcional)"
@@ -199,6 +220,8 @@ locals {
199220 projects_mount_host_path = local. persist_projects_storage && local. workspace_storage_root != " " ? local. workspace_storage_projects : " "
200221 opencode_default_base_url = trimspace (var. opencode_default_base_url )
201222 mks_key_endpoint = trimspace (var. mks_key_endpoint )
223+ freeapi_base_url = trimspace (var. freeapi_base_url )
224+ freeapi_key_endpoint = trimspace (var. freeapi_key_endpoint )
202225 home_volume_resolved = " coder-${ data . coder_workspace . me . id } -home"
203226 docker_data_volume_name = " coder-${ data . coder_workspace . me . id } -docker-data"
204227 repo_url = trimspace (data. coder_parameter . p04_git_repo_url . value )
@@ -207,6 +230,7 @@ locals {
207230 openai_base_url = trimspace (data. coder_parameter . p05_opencode_provider_url . value )
208231 openai_api_key = trimspace (data. coder_parameter . p05_opencode_api_key . value )
209232 auto_provision_mks_key = data. coder_parameter . p05_autoprovision_mks_key . value
233+ auto_provision_freeapi_key = data. coder_parameter . p05_autoprovision_freeapi_key . value
210234 claude_token = trimspace (data. coder_parameter . p05_claude_token . value )
211235 install_claude = trimspace (data. coder_parameter . p05_claude_token . value ) != " "
212236 vscode_extensions_default = [
@@ -440,6 +464,32 @@ JSONCFG
440464 fi
441465 fi
442466
467+ # Autoprovisionar clave FreeAPI si está habilitado y hay endpoint/base URL
468+ freeapi_auto_flag="$${AUTO_PROVISION_FREEAPI_API_KEY:-true}"
469+ if printf '%s' "$freeapi_auto_flag" | grep -Eq '^(1|true|TRUE|yes|on)$'; then
470+ FREEAPI_BASE_URL="$${FREEAPI_BASE_URL:-}"
471+ export FREEAPI_BASE_URL
472+ freeapi_payload=""
473+ if [ -z "$${FREEAPI_API_KEY:-}" ]; then
474+ FREEAPI_ENDPOINT="$${FREEAPI_KEY_ENDPOINT:-}"
475+ if [ -z "$FREEAPI_ENDPOINT" ]; then
476+ echo "FREEAPI_KEY_ENDPOINT no configurado; omitiendo autoprovision de key FreeAPI" >&2
477+ else
478+ freeapi_alias="freeapi-$(tr -dc 0-9 </dev/urandom 2>/dev/null | head -c 8 | sed 's/^$/00000000/')"
479+ freeapi_payload=$(printf '{"email":"%s","alias":"%s"}' "$${CODER_USER_EMAIL:-}" "$freeapi_alias")
480+ freeapi_resp=$(curl -fsSL -X POST "$FREEAPI_ENDPOINT" -H "Content-Type: application/json" -d "$freeapi_payload" 2>/dev/null || true)
481+ freeapi_key=$(printf '%s' "$freeapi_resp" | python3 -c 'import sys,json;print(json.load(sys.stdin).get("key",""))' 2>/dev/null || true)
482+ if [ -n "$freeapi_key" ]; then
483+ FREEAPI_API_KEY="$freeapi_key"
484+ export FREEAPI_API_KEY
485+ mkdir -p /home/coder/.opencode
486+ printf "%s" "$freeapi_key" > /home/coder/.opencode/.latest_freeapi_key || true
487+ printf "%s" "$freeapi_payload" > /home/coder/.opencode/.latest_freeapi_request || true
488+ fi
489+ fi
490+ fi
491+ fi
492+
443493 # Propagar variables a nuevas shells interactivas
444494 if [ -n "$${OPENCODE_PROVIDER_URL:-}" ]; then
445495 MKS_BASE_URL="$${MKS_BASE_URL:-$OPENCODE_PROVIDER_URL}"
@@ -461,6 +511,16 @@ JSONCFG
461511 echo "export OPENCODE_API_KEY=\"$OPENCODE_API_KEY\"" >> ~/.bashrc
462512 fi
463513 fi
514+ if [ -n "$${FREEAPI_BASE_URL:-}" ]; then
515+ if ! grep -q "FREEAPI_BASE_URL=" ~/.bashrc 2>/dev/null; then
516+ echo "export FREEAPI_BASE_URL=\"$FREEAPI_BASE_URL\"" >> ~/.bashrc
517+ fi
518+ fi
519+ if [ -n "$${FREEAPI_API_KEY:-}" ]; then
520+ if ! grep -q "FREEAPI_API_KEY=" ~/.bashrc 2>/dev/null; then
521+ echo "export FREEAPI_API_KEY=\"$FREEAPI_API_KEY\"" >> ~/.bashrc
522+ fi
523+ fi
464524
465525 # Instalar OpenCode CLI si Claude está activo (sin módulo)
466526 if printf '%s' "$${INSTALL_CLAUDE:-false}" | grep -Eq '^(1|true|TRUE|yes|on)$'; then
@@ -544,7 +604,7 @@ GENMKS
544604 sudo chmod +x /usr/local/bin/gen_mks_litellm_key || true
545605
546606 # Config inicial de OpenCode (opcional)
547- if [ -n "$${OPENCODE_API_KEY:-}" ]; then
607+ if [ -n "$${OPENCODE_API_KEY:-}" ] || [ -n "$${FREEAPI_API_KEY:-}" ] ; then
548608 mkdir -p /home/coder/.opencode
549609 cat > /home/coder/.opencode/opencode.json <<'JSONCFG'
550610{
@@ -787,13 +847,16 @@ GENMKS
787847 "api_key": "OPENCODE_API_KEY_VALUE"
788848 },
789849 "models": {
790- "devstral:24b": { "name": "Devstral 24b" },
791- "qwen2.5-coder:14b ": { "name": "Qwen2.5 Coder 14b " },
792- "qwen2.5-coder:7b": { "name": "Qwen2.5 Coder 7b" },
850+ "devstral-small-2 :24b": { "name": "Devstral Small 2 24b" },
851+ "opencoder-8b-base ": { "name": "OpenCoder 8b Base " },
852+ "qwen2.5-coder:7b-base ": { "name": "Qwen2.5 Coder 7b Base " },
793853 "qwen3-coder:30b": { "name": "Qwen3 Coder 30b" },
854+ "qwen3.5:27b": { "name": "Qwen3.5 27b" },
855+ "qwen3:32b": { "name": "Qwen3 32b" },
856+ "qwen3:14b": { "name": "Qwen3 14b" },
857+ "qwen3:8b": { "name": "Qwen3 8b" },
794858 "gpt-oss:20b": { "name": "GPT-OSS 20b" },
795- "magistral:24b": { "name": "Magistral 24b" },
796- "mistral-small3.1:24b": { "name": "Mistral Small3.1 24b" }
859+ "gpt-oss-safeguard:latest": { "name": "GPT-OSS Safeguard" }
797860 }
798861 },
799862 "google": {
@@ -829,6 +892,71 @@ JSONCFG
829892 base_url="$${OPENCODE_PROVIDER_URL:-$${OPENCODE_DEFAULT_BASE_URL:-}}"
830893 sed -i "s|OPENCODE_PROVIDER_URL_VALUE|$base_url|g" /home/coder/.opencode/opencode.json
831894 sed -i "s|OPENCODE_API_KEY_VALUE|$${OPENCODE_API_KEY}|g" /home/coder/.opencode/opencode.json
895+ FREEAPI_BASE="$${FREEAPI_BASE_URL:-}"
896+ FREEAPI_KEY="$${FREEAPI_API_KEY:-}"
897+ FREEAPI_BASE="$${FREEAPI_BASE%/}"
898+ if [ -n "$FREEAPI_BASE" ]; then
899+ FREEAPI_BASE_URL="$FREEAPI_BASE" FREEAPI_API_KEY="$FREEAPI_KEY" python3 - <<'PY'
900+ import json, os, urllib.request
901+
902+ path = "/home/coder/.opencode/opencode.json"
903+ base_url = (os.environ.get("FREEAPI_BASE_URL") or "").strip().rstrip("/")
904+ api_key = (os.environ.get("FREEAPI_API_KEY") or "").strip()
905+ if not base_url:
906+ raise SystemExit(0)
907+
908+ with open(path, "r", encoding="utf-8") as f:
909+ data = json.load(f)
910+
911+ def norm_model_id(raw):
912+ if not isinstance(raw, str):
913+ return ""
914+ s = raw.strip()
915+ if not s:
916+ return ""
917+ if "//" in s:
918+ s = s.split("//", 1)[1]
919+ if "/" in s:
920+ s = s.rsplit("/", 1)[-1]
921+ return s
922+
923+ model_ids = []
924+ for p in ("/v1/models", "/models"):
925+ try:
926+ headers = {"Accept": "application/json"}
927+ if api_key:
928+ headers["Authorization"] = f"Bearer {api_key}"
929+ req = urllib.request.Request(f"{base_url}{p}", headers=headers)
930+ with urllib.request.urlopen(req, timeout=10) as resp:
931+ payload = json.loads(resp.read().decode("utf-8", "replace"))
932+ items = payload.get("data", payload if isinstance(payload, list) else [])
933+ for item in items:
934+ if not isinstance(item, dict):
935+ continue
936+ mid = norm_model_id(item.get("id"))
937+ if mid.endswith("-ha"):
938+ model_ids.append(mid)
939+ if model_ids:
940+ break
941+ except Exception:
942+ continue
943+
944+ models = {}
945+ for mid in sorted(set(model_ids)):
946+ models[mid] = {"name": mid}
947+
948+ provider = data.setdefault("provider", {})
949+ provider["freeapi"] = {
950+ "npm": "@ai-sdk/openai-compatible",
951+ "name": "FreeAPI",
952+ "options": {"baseURL": base_url, "api_key": api_key},
953+ "models": models,
954+ }
955+
956+ with open(path, "w", encoding="utf-8") as f:
957+ json.dump(data, f, indent=2, ensure_ascii=False)
958+ PY
959+ fi
832960 chown -R "$USER:$USER" /home/coder/.opencode || true
833961 fi
834962
@@ -895,9 +1023,12 @@ JSONCFG
8951023 OPENCODE_API_KEY = local.openai_api_key
8961024 OPENCODE_DEFAULT_BASE_URL = local.opencode_default_base_url
8971025 MKS_KEY_ENDPOINT = local.mks_key_endpoint
1026+ FREEAPI_BASE_URL = local.freeapi_base_url
1027+ FREEAPI_KEY_ENDPOINT = local.freeapi_key_endpoint
8981028 MKS_BASE_URL = local.openai_base_url
8991029 MKS_API_KEY = local.openai_api_key
9001030 AUTO_PROVISION_MKS_API_KEY = tostring (local. auto_provision_mks_key )
1031+ AUTO_PROVISION_FREEAPI_API_KEY = tostring (local. auto_provision_freeapi_key )
9011032 CODER_USER_EMAIL = data.coder_workspace_owner.me.email
9021033 INSTALL_CLAUDE = tostring (local. install_claude )
9031034 AGENTAPI_CHAT_BASE_PATH = local.install_claude ? " /@${ data . coder_workspace_owner . me . name } /${ data . coder_workspace . me . id } /apps/ccw/chat" : " /@${ data . coder_workspace_owner . me . name } /${ data . coder_workspace . me . id } /apps/opencode/chat"
0 commit comments