refactor: ai — merge gemini/openrouter into unified plugin architecture
gates / consistency-and-conventions (push) Successful in 1m59s
gates / consistency-and-conventions (push) Successful in 1m59s
- bin/pos-ai: single provider-agnostic tool (ask/chat/sessions/capture/models/providers) - lib/ai-providers/gemini.sh: Gemini adapter (59 ln) - lib/ai-providers/openrouter.sh: OpenRouter adapter (59 ln) - bin/pos-ai-gemini/openrouter: thin forwarders for backward compat - Provider adapter interface: provider_name/default_model/generate/models_list - Unified session format (OpenAI messages), auto-migrate old gemini contents - Config: AI_PROVIDER/AI_API_KEY/AI_MODEL/AI_SYSTEM_PROMPT in ai.env - Config fallback: AI_API_KEY → provider-specific env var → error - Default system prompt configurable via AI_SYSTEM_PROMPT - New subcommand: pos ai providers (lists providers + config status) - Shell hook (pos-ai-hook.sh) for auto-capture
This commit is contained in:
Executable
+59
@@ -0,0 +1,59 @@
|
||||
#!/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_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
|
||||
body="$(printf '%s' "$messages" | jq -c '{
|
||||
contents: [.messages[]? | {role: (.role | gsub("assistant";"model")), parts: [{text: .content}]}]
|
||||
}')"
|
||||
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
|
||||
}
|
||||
Executable
+59
@@ -0,0 +1,59 @@
|
||||
#!/usr/bin/env bash
|
||||
# OpenRouter 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_name() { printf 'OpenRouter'; }
|
||||
provider_default_model() { printf 'openrouter/auto'; }
|
||||
|
||||
# $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
|
||||
if [ -n "$system" ]; then
|
||||
body="$(printf '%s' "$messages" | jq -c --arg s "$system" \
|
||||
'[{role:"system",content:$s}] + .messages')"
|
||||
else
|
||||
body="$(printf '%s' "$messages" | jq -c '.messages')"
|
||||
fi
|
||||
body="$(printf '%s' "$body" | jq -nc --arg m "$model" --argjson msgs "$body" \
|
||||
'{model:$m, messages:$msgs}')"
|
||||
resp="$(curl -sS -m 60 -X POST "https://openrouter.ai/api/v1/chat/completions" \
|
||||
-H "Authorization: Bearer ${AI_API_KEY}" \
|
||||
-H "Content-Type: application/json" \
|
||||
-H "HTTP-Referer: https://github.com/admin/Linux_post_install" \
|
||||
--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 '.choices[0].message.content // ""'
|
||||
}
|
||||
|
||||
# $1=current default model → stdout=formatted model list
|
||||
provider_models_list() {
|
||||
local model="$1" resp code body m
|
||||
resp="$(curl -sS -m 30 "https://openrouter.ai/api/v1/models" \
|
||||
-H "Authorization: Bearer ${AI_API_KEY}" \
|
||||
--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 '.data[]?.id' | sort)"
|
||||
echo "OpenRouter models:"
|
||||
while IFS= read -r m; do
|
||||
[ -n "$m" ] || continue
|
||||
if [ "$m" = "$model" ]; then
|
||||
printf ' %-48s <- default\n' "$m"
|
||||
else
|
||||
printf ' %-48s\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 OPENROUTER_MODEL"
|
||||
fi
|
||||
}
|
||||
Reference in New Issue
Block a user