01aa7f3e8f
gates / consistency-and-conventions (push) Successful in 32s
User hit 'API error 402: ... You requested up to 131072 tokens, but can only afford 4511' on the assist alias: no provider ever sent max_tokens, so OpenRouter's credit pre-check billed the routed model's full worst-case output; user also asked to bound session history to the last 5 requests/responses. Architect decisions: - AI_MAX_TOKENS (num, default 2048): sent as max_tokens on OpenRouter and generationConfig.maxOutputTokens on Gemini — a real per-request cost ceiling. llamacpp unchanged (local/free, no pre-check). - AI_SESSION_TURNS (num, default 40 kept back-compat; messages, 2 per exchange — 10 = last 5 conversations): resolved lazily in session_push because config loads after the hardcoded line-25 default. - Both registered in the bin/pos-ai POS_CONFIG @General section, so they appear in 'pos config ai' with num: validation. Reviewer hardening (CHANGES_REQUIRED -> fixed): unguarded env input could reach jq tonumber (0/-5/010/abc all savable via config-ui's ^-?[0-9]+$) and abort the CLI; both providers and session_push now guard with ^[1-9][0-9]*$ and fall back to the default. Verified: fake-curl shim smoke (16 provider-body + 12 session-window checks incl. the 010-regression proof), make gen idempotent, make check OK, make lint 0 FAIL/0 WARN, make test 17 files / 299 checks / 0 fail (~49s), bash -n clean, git diff --check clean. Reviewer ACCEPT (twice). Tester regression round (permanent provider-body + session-pruning coverage) intentionally not run this cycle — user's call; remains a documented follow-up.
67 lines
3.1 KiB
Bash
Executable File
67 lines
3.1 KiB
Bash
Executable File
#!/usr/bin/env bash
|
|
# Gemini provider adapter for pos-ai
|
|
# Provider-specific: API call, auth, response parsing, models list
|
|
# Part of the R8 provider-agnostic architecture (lib/ai-providers/).
|
|
|
|
# Provider-specific config variables (auto-discovered by pos config ai):
|
|
# PROVIDER_CONFIG: AI_GEMINI_API_KEY=secret:Gemini API key from aistudio.google.com
|
|
# PROVIDER_CONFIG: AI_GEMINI_MODEL=:Gemini model id (default: gemini-2.5-flash)
|
|
|
|
provider_name() { printf 'Google Gemini'; }
|
|
provider_default_model() { printf 'gemini-2.5-flash'; }
|
|
|
|
# $1=model $2=messages JSON ({"messages":[{role,content}]}) $3=optional system prompt
|
|
provider_generate() {
|
|
local model="$1" messages="$2" system="${3:-}" body resp code body_out errmsg
|
|
# Convert OpenAI messages format to Gemini contents format
|
|
local mt="${AI_MAX_TOKENS:-2048}"
|
|
[[ "$mt" =~ ^[1-9][0-9]*$ ]] || mt=2048
|
|
body="$(printf '%s' "$messages" | jq -c --arg mt "$mt" '{
|
|
contents: [.messages[]? | {role: (.role | gsub("assistant";"model")), parts: [{text: .content}]}],
|
|
generationConfig: {maxOutputTokens: ($mt|tonumber)}
|
|
}')"
|
|
if [ -n "$system" ]; then
|
|
body="$(printf '%s' "$body" | jq -c --arg s "$system" \
|
|
'. + {systemInstruction:{role:"system",parts:[{text:$s}]}}')"
|
|
fi
|
|
resp="$(curl -sS -m 60 -X POST "https://generativelanguage.googleapis.com/v1beta/models/${model}:generateContent" \
|
|
-H "x-goog-api-key: ${AI_API_KEY}" \
|
|
-H "Content-Type: application/json" \
|
|
--write-out $'\n%{http_code}' \
|
|
--data "$body")" || { echo "request failed (curl exit $?)" >&2; return 1; }
|
|
code="${resp##*$'\n'}"
|
|
body_out="${resp%$'\n'*}"
|
|
if [ "$code" != "200" ]; then
|
|
errmsg="$(printf '%s' "$body_out" | jq -r '.error.message // empty' 2>/dev/null || true)"
|
|
echo "API error $code${errmsg:+: $errmsg}" >&2
|
|
return 1
|
|
fi
|
|
printf '%s' "$body_out" | jq -r '[.candidates[0].content.parts[]?.text] | join("")'
|
|
}
|
|
|
|
# $1=current default model → stdout=formatted model list
|
|
provider_models_list() {
|
|
local model="$1" resp code body m
|
|
resp="$(curl -sS -m 30 -G "https://generativelanguage.googleapis.com/v1beta/models" \
|
|
-H "x-goog-api-key: ${AI_API_KEY}" \
|
|
--data-urlencode "pageSize=1000" \
|
|
--write-out $'\n%{http_code}')" || err "request failed (curl exit $?)"
|
|
code="${resp##*$'\n'}"
|
|
body="${resp%$'\n'*}"
|
|
[ "$code" = "200" ] || err "API error $code: $(printf '%s' "$body" | jq -r '.error.message // empty')"
|
|
local list
|
|
list="$(printf '%s' "$body" | jq -r '.models[]? | select((.supportedGenerationMethods // []) | index("generateContent")) | .name' | sed 's#^models/##' | sort)"
|
|
echo "Gemini models (generateContent-capable):"
|
|
while IFS= read -r m; do
|
|
[ -n "$m" ] || continue
|
|
if [ "$m" = "$model" ]; then
|
|
printf ' %-32s <- default\n' "$m"
|
|
else
|
|
printf ' %-32s\n' "$m"
|
|
fi
|
|
done <<< "$list"
|
|
if ! grep -qxF "$model" <<< "$list" 2>/dev/null; then
|
|
warn "configured default '$model' is not in the list — set AI_MODEL or AI_GEMINI_MODEL"
|
|
fi
|
|
}
|