Files
hermes 9dc1c203cb
ci / lint (pull_request) Successful in 14s
fix(agents): declare workflow_call secrets + diagnose empty XAI_API_KEY
Cross-owner reusable calls left XAI_API_KEY empty in the runner while
OLLAMA_CLOUD_API_KEY worked. Declare secrets on workflow_call, accept
alternate secret names, log key lengths (not values), and ship an
ai-agent.yml caller template with an explicit secrets map.
2026-07-30 16:47:13 +03:00

80 lines
5.4 KiB
Bash
Executable File

#!/usr/bin/env bash
# Install opencode + provider config (+ Playwright MCP for browser agents).
#
# Required env (provided by the workflow step): OLLAMA_URL OLLAMA_CLOUD_API_KEY XAI_API_KEY
# NAME SKILLS GITHUB_PATH HOME
set -eu
# Non-secret diagnostics — prove which provider keys reached the runner (length only).
echo "provider key lengths: OLLAMA_URL=${#OLLAMA_URL} OLLAMA_CLOUD_API_KEY=${#OLLAMA_CLOUD_API_KEY} XAI_API_KEY=${#XAI_API_KEY}"
if [ -z "${XAI_API_KEY:-}" ]; then
echo "WARNING: XAI_API_KEY is empty in this job. xai-oc fallback will fail."
echo "Fix: set Actions secret XAI_API_KEY on the CALLER repo (e.g. ffaerber/homelab),"
echo "not only on gitea/agents. Name must be exact: XAI_API_KEY"
fi
# PIN the opencode version: an unpinned `latest` means a breaking release (CLI flags, or the
# --format json event schema that build-activity-log.sh parses) breaks every agent in every repo
# at once. Bump deliberately by changing this default (or set OPENCODE_VERSION in the step env).
OPENCODE_VERSION="${OPENCODE_VERSION:-1.17.13}"
# Skip the download when a cache hit already restored the pinned binary (see the Cache
# opencode CLI step in agent.yml). The installer always re-fetches otherwise.
OC_BIN="$HOME/.opencode/bin/opencode"
if [ -x "$OC_BIN" ] && "$OC_BIN" --version 2>/dev/null | grep -qF "$OPENCODE_VERSION"; then
echo "opencode $OPENCODE_VERSION already present (cache hit) — skipping install"
else
curl -fsSL https://opencode.ai/install | bash -s -- --version "$OPENCODE_VERSION"
fi
echo "$HOME/.opencode/bin" >> "$GITHUB_PATH"
mkdir -p ~/.config/opencode
# Playwright browser MCP only for agents that need to drive a web app
MCP='{}'
case "$NAME" in
senior|lead|qa)
echo "Enabling Playwright MCP for @$NAME"
MCP='{"playwright":{"type":"local","command":["npx","-y","@playwright/mcp@latest","--headless"],"enabled":true}}'
npx -y playwright install --with-deps chromium || npx -y playwright install chromium || true
;;
esac
# Per-agent skill scoping. Skills are loaded on-demand by opencode: only a skill's one-line
# `description` ever appears in an agent's <available_skills> list, and the full SKILL.md body
# (curl/API how-to) is loaded ONLY when the agent calls the `skill` tool — it is never in any
# system prompt. To also hide the summary from agents that shouldn't use a skill, we deny all
# skills by default and allow only the ones in this agent's registry list (passed via $SKILLS).
# A denied skill is hidden entirely (name + description omitted), so e.g. @junior never sees
# gitea-api at all; it just knows from the roster that @senior/@lead can, and asks them.
SKILLS="${SKILLS:-[]}"
PERM=$(jq -nc --argjson s "$SKILLS" '
{skill: ( {"*":"deny"} + (reduce $s[] as $k ({}; . + {($k):"allow"})) )}')
# Three OpenAI-compatible providers: local self-hosted ollama (ornith) + Ollama Cloud
# (gemma4/kimi-k2.7-code/glm-5.2/minimax-m3) + xAI (grok-4.5). The provider `models:` maps are
# DERIVED from agents.json (the single source of truth, shared with route.sh) so every model an
# agent is routed to is always declared in the provider config. `ollama-cloud/` prefix models go to
# the cloud provider; `ollama/` prefix models go to the local provider; `xai-oc/` prefix models go
# to our xAI shim (OpenAI-compatible, https://api.x.ai/v1). No other built-in providers remain.
#
# The provider key is `xai-oc`, NOT `xai` — opencode ships a built-in `xai` entry in its own model
# catalog (a real @ai-sdk/xai integration that implements the Responses API). Naming our custom
# openai-compatible shim `xai` collides with that catalog entry: opencode's --auto permission-check
# path then assumes the catalog's provider (which has `.responses()`) and crashes with
# "Z.responses is not a function" on every run once a `permission` block is present (i.e. always in
# this pipeline). Confirmed by reproducing locally with the production config shape on opencode
# 1.17.13 — renaming the key to `xai-oc` avoids the collision entirely. See issue #118.
# See issue #31.
AGENTS_JSON="${SCRIPTS:-$(dirname -- "$0")}/agents.json"
# Include primary `.model` AND optional `.fallback` so failover models are always registered
# in opencode provider maps (issue: Ollama Cloud quota → xAI).
CLOUD_MODELS=$(jq -r '[.[] | (.model, .fallback) | select(type=="string" and startswith("ollama-cloud/")) | sub("^ollama-cloud/";"")] | unique | map({(.):{}}) | add // {}' "$AGENTS_JSON")
LOCAL_MODELS=$(jq -r '[.[] | (.model, .fallback) | select(type=="string" and startswith("ollama/")) | sub("^ollama/";"")] | unique | map({(.):{}}) | add // {"ornith:35b":{}}' "$AGENTS_JSON")
XAI_MODELS=$(jq -r '[.[] | (.model, .fallback) | select(type=="string" and startswith("xai-oc/")) | sub("^xai-oc/";"")] | unique | map({(.):{}}) | add // {}' "$AGENTS_JSON")
jq -n --argjson mcp "$MCP" --argjson perm "$PERM" --argjson cloud "$CLOUD_MODELS" --argjson local "$LOCAL_MODELS" --argjson xai "$XAI_MODELS" --arg url "$OLLAMA_URL" --arg ckey "$OLLAMA_CLOUD_API_KEY" --arg xkey "$XAI_API_KEY" '{
provider: {
ollama: {npm:"@ai-sdk/openai-compatible", options:{baseURL:($url+"/v1")}, models:$local},
"ollama-cloud": {npm:"@ai-sdk/openai-compatible", options:{baseURL:"https://ollama.com/v1", apiKey:$ckey}, models:$cloud},
"xai-oc": {npm:"@ai-sdk/openai-compatible", options:{baseURL:"https://api.x.ai/v1", apiKey:$xkey}, models:$xai}
},
permission: $perm,
mcp: $mcp
}' > ~/.config/opencode/opencode.json
echo "opencode config (secrets masked):"; cat ~/.config/opencode/opencode.json