239 lines
6.9 KiB
Bash
Executable file
239 lines
6.9 KiB
Bash
Executable file
#!/usr/bin/env bash
|
|
set -euo pipefail
|
|
|
|
PI_DIR="$HOME/.pi/agent"
|
|
SETTINGS="$PI_DIR/settings.json"
|
|
MODELS="$PI_DIR/models.json"
|
|
PYTHON_BIN="/usr/bin/python3"
|
|
|
|
if [[ ! -x "$PYTHON_BIN" ]]; then
|
|
PYTHON_BIN="$(command -v python3 || true)"
|
|
fi
|
|
|
|
if [[ -z "$PYTHON_BIN" || ! -x "$PYTHON_BIN" ]]; then
|
|
echo "python3 를 찾을 수 없습니다."
|
|
exit 1
|
|
fi
|
|
|
|
mkdir -p "$PI_DIR"
|
|
|
|
"$PYTHON_BIN" - "$SETTINGS" "$MODELS" <<'PY'
|
|
import json
|
|
import pathlib
|
|
import subprocess
|
|
import sys
|
|
import time
|
|
|
|
settings_path = pathlib.Path(sys.argv[1]).expanduser()
|
|
models_path = pathlib.Path(sys.argv[2]).expanduser()
|
|
|
|
|
|
def read_json(path):
|
|
if not path.exists():
|
|
return {}
|
|
return json.loads(path.read_text() or "{}")
|
|
|
|
|
|
def write_json(path, value):
|
|
if path.exists():
|
|
backup = path.with_name(f"{path.name}.bak-{time.strftime('%Y%m%d%H%M%S')}")
|
|
backup.write_bytes(path.read_bytes())
|
|
path.write_text(json.dumps(value, indent=2, ensure_ascii=False) + "\n")
|
|
|
|
|
|
def run_helper(path):
|
|
expanded = pathlib.Path(path).expanduser()
|
|
if not expanded.exists():
|
|
return ""
|
|
try:
|
|
result = subprocess.run(
|
|
[str(expanded)],
|
|
check=True,
|
|
stdout=subprocess.PIPE,
|
|
stderr=subprocess.DEVNULL,
|
|
text=True,
|
|
timeout=10,
|
|
)
|
|
except Exception:
|
|
return ""
|
|
return result.stdout.strip()
|
|
|
|
|
|
def merge_desired_models(existing_models, desired_models):
|
|
existing_by_id = {}
|
|
for existing in existing_models:
|
|
if isinstance(existing, dict) and isinstance(existing.get("id"), str):
|
|
existing_by_id.setdefault(existing["id"], existing)
|
|
|
|
merged_models = []
|
|
for desired in desired_models:
|
|
merged = dict(existing_by_id.get(desired["id"], {}))
|
|
merged.update(desired)
|
|
merged_models.append(merged)
|
|
return merged_models
|
|
|
|
|
|
def zero_cost():
|
|
return {"input": 0, "output": 0, "cacheRead": 0, "cacheWrite": 0}
|
|
|
|
|
|
settings = read_json(settings_path)
|
|
models_doc = read_json(models_path)
|
|
|
|
providers = models_doc.setdefault("providers", {})
|
|
|
|
seulgivibe_provider = providers.setdefault("seulgivibe-claude", {})
|
|
seulgivibe_api_key = seulgivibe_provider.get("apiKey") or run_helper("~/.claude/anthropic_key.sh")
|
|
seulgivibe_provider.update(
|
|
{
|
|
"baseUrl": "https://seulgivibe.lgudax.cool/anthropic",
|
|
"api": "anthropic-messages",
|
|
"authHeader": True,
|
|
"compat": {"supportsEagerToolInputStreaming": False},
|
|
}
|
|
)
|
|
if seulgivibe_api_key:
|
|
seulgivibe_provider["apiKey"] = seulgivibe_api_key
|
|
seulgivibe_provider["models"] = merge_desired_models(
|
|
seulgivibe_provider.get("models", []),
|
|
[
|
|
{
|
|
"id": "claude-sonnet-4-5",
|
|
"name": "Seulgivibe Claude Sonnet 4.5",
|
|
"reasoning": True,
|
|
"input": ["text"],
|
|
"contextWindow": 200000,
|
|
"maxTokens": 64000,
|
|
"cost": zero_cost(),
|
|
},
|
|
{
|
|
"id": "claude-opus-4-8",
|
|
"name": "Seulgivibe Claude Opus 4.8",
|
|
"reasoning": True,
|
|
"thinkingLevelMap": {"xhigh": "xhigh"},
|
|
"input": ["text"],
|
|
"contextWindow": 1000000,
|
|
"maxTokens": 128000,
|
|
"compat": {"forceAdaptiveThinking": True, "supportsTemperature": False},
|
|
"cost": zero_cost(),
|
|
},
|
|
{
|
|
"id": "claude-fable-5",
|
|
"name": "Seulgivibe Claude Fable 5",
|
|
"reasoning": True,
|
|
"thinkingLevelMap": {"xhigh": "xhigh"},
|
|
"input": ["text"],
|
|
"contextWindow": 1000000,
|
|
"maxTokens": 128000,
|
|
"compat": {"forceAdaptiveThinking": True, "supportsTemperature": False},
|
|
"cost": zero_cost(),
|
|
},
|
|
],
|
|
)
|
|
|
|
legacy_openai_provider = providers.get("seulgivibe-openai")
|
|
if not isinstance(legacy_openai_provider, dict):
|
|
legacy_openai_provider = {}
|
|
|
|
seulgivibe_codex_provider = providers.setdefault("seulgivibe-codex", {})
|
|
seulgivibe_codex_api_key = (
|
|
seulgivibe_codex_provider.get("apiKey")
|
|
or legacy_openai_provider.get("apiKey")
|
|
or run_helper("~/.claude/anthropic_key.sh")
|
|
)
|
|
seulgivibe_codex_provider.update(
|
|
{
|
|
"baseUrl": "https://seulgivibe.lgudax.cool/openai/v1",
|
|
"api": "openai-responses",
|
|
}
|
|
)
|
|
if seulgivibe_codex_api_key:
|
|
seulgivibe_codex_provider["apiKey"] = seulgivibe_codex_api_key
|
|
seulgivibe_codex_provider["models"] = merge_desired_models(
|
|
seulgivibe_codex_provider.get("models", []),
|
|
[
|
|
{
|
|
"id": "gpt-5.1",
|
|
"name": "Seulgivibe Codex GPT-5.1",
|
|
"reasoning": True,
|
|
"thinkingLevelMap": {"off": "none"},
|
|
"input": ["text"],
|
|
"contextWindow": 400000,
|
|
"maxTokens": 128000,
|
|
"cost": zero_cost(),
|
|
},
|
|
{
|
|
"id": "gpt-5.5",
|
|
"name": "Seulgivibe Codex GPT-5.5",
|
|
"reasoning": True,
|
|
"thinkingLevelMap": {"off": "none", "xhigh": "xhigh", "minimal": None},
|
|
"input": ["text"],
|
|
"contextWindow": 272000,
|
|
"maxTokens": 128000,
|
|
"cost": zero_cost(),
|
|
},
|
|
],
|
|
)
|
|
providers.pop("seulgivibe-openai", None)
|
|
|
|
settings.setdefault("defaultProvider", "seulgivibe-codex")
|
|
settings.setdefault("defaultModel", "gpt-5.5")
|
|
settings.setdefault("defaultThinkingLevel", "high")
|
|
settings.setdefault("hideThinkingBlock", False)
|
|
settings["httpIdleTimeoutMs"] = 300000
|
|
retry = settings.setdefault("retry", {})
|
|
provider_retry = retry.setdefault("provider", {})
|
|
provider_retry["timeoutMs"] = 300000
|
|
provider_retry["maxRetries"] = 0
|
|
provider_retry["maxRetryDelayMs"] = 60000
|
|
|
|
extensions = settings.get("extensions", [])
|
|
if not isinstance(extensions, list):
|
|
extensions = []
|
|
sampling_extension_entry = "extensions/openai-sampling-parameters.ts"
|
|
normalized_extensions = []
|
|
seen_extensions = set()
|
|
for item in extensions:
|
|
if (
|
|
not isinstance(item, str)
|
|
or item == sampling_extension_entry
|
|
or item in seen_extensions
|
|
):
|
|
continue
|
|
normalized_extensions.append(item)
|
|
seen_extensions.add(item)
|
|
settings["extensions"] = normalized_extensions
|
|
|
|
enabled = settings.setdefault("enabledModels", [])
|
|
if not isinstance(enabled, list):
|
|
enabled = []
|
|
desired_enabled = [
|
|
"seulgivibe-codex/gpt-5.1:medium",
|
|
"seulgivibe-codex/gpt-5.5:xhigh",
|
|
"seulgivibe-claude/claude-sonnet-4-5",
|
|
"seulgivibe-claude/claude-opus-4-8",
|
|
"seulgivibe-claude/claude-fable-5",
|
|
]
|
|
normalized_enabled = []
|
|
seen_enabled = set()
|
|
for item in enabled:
|
|
if not isinstance(item, str):
|
|
continue
|
|
if item.startswith("seulgivibe-openai/"):
|
|
continue
|
|
if item.startswith("seulgivibe-codex/") or item.startswith("seulgivibe-claude/"):
|
|
continue
|
|
if item not in seen_enabled:
|
|
normalized_enabled.append(item)
|
|
seen_enabled.add(item)
|
|
for item in desired_enabled:
|
|
if item not in seen_enabled:
|
|
normalized_enabled.append(item)
|
|
seen_enabled.add(item)
|
|
settings["enabledModels"] = normalized_enabled
|
|
|
|
write_json(settings_path, settings)
|
|
write_json(models_path, models_doc)
|
|
PY
|
|
|
|
echo "완료: Pi session에서 /reload 하거나 Pi를 재시작하세요."
|