Opencode improvements

This commit is contained in:
Samuel Aubertin
2026-04-15 03:31:04 +02:00
parent 6ca643830f
commit 273e42dd2d
8 changed files with 537 additions and 165 deletions

512
sloptrap
View File

@@ -242,7 +242,11 @@ ALLOW_HOST_NETWORK=false
BACKEND="codex"
OPENCODE_SERVER=""
OPENCODE_MODEL=""
OPENCODE_CONTEXT=""
OPENCODE_STATE_HOME_HOST=""
OPENCODE_CONFIG_HOST=""
OPENCODE_CONFIG_CONT=""
SLOPTRAP_HOST_ALIAS=""
declare -a SLOPTRAP_TEMP_PATHS=()
@@ -302,24 +306,35 @@ create_temp_dir() {
write_embedded_dockerfile() {
if [[ "$BACKEND" == "opencode" ]]; then
cat <<'EOF'
local opencode_pkg
case "$(uname -m)" in
x86_64|amd64)
opencode_pkg="opencode-desktop-linux-amd64.deb"
;;
arm64|aarch64)
opencode_pkg="opencode-desktop-linux-arm64.deb"
;;
*)
error "unsupported architecture for opencode"
;;
esac
local dockerfile_content
dockerfile_content=$(cat <<'DOCKERFILE_EOF'
# Dockerfile.sloptrap
ARG BASE_IMAGE=debian:trixie-slim
FROM ${BASE_IMAGE}
ENV DEBIAN_FRONTEND=noninteractive
ARG BASE_PACKAGES="curl bash ca-certificates libstdc++6 ripgrep xxd file procps util-linux"
ARG EXTRA_PACKAGES=""
RUN apt-get update \
&& apt-get install -y --no-install-recommends apt-utils ${BASE_PACKAGES} ${EXTRA_PACKAGES} \
&& rm -rf /var/lib/apt/lists/*
ARG BASE_PACKAGES="curl bash ca-certificates libstdc++6 wget ripgrep xxd file procps util-linux binutils"
RUN apt-get update && apt-get install -y --no-install-recommends apt-utils ${BASE_PACKAGES} ${EXTRA_PACKAGES} && rm -rf /var/lib/apt/lists/*
ARG CODEX_UID=1337
ARG CODEX_GID=1337
RUN groupadd --gid ${CODEX_GID} sloptrap \
&& useradd --create-home --home-dir /home/sloptrap \
--gid sloptrap --uid ${CODEX_UID} --shell /bin/bash sloptrap
--gid sloptrap --uid ${CODEX_UID} --shell /bin/bash sloptrap
ARG OPENCODE_BIN=opencode
ARG OPENCODE_CONF=config/config.toml
@@ -330,8 +345,12 @@ RUN chmod 0755 /usr/local/bin/opencode \
WORKDIR /workspace
ENV SHELL=/bin/bash HOME=/home/sloptrap
ENTRYPOINT ["/usr/local/bin/opencode"]
EOF
ENTRYPOINT ["opencode"]
DOCKERFILE_EOF
)
# Replace placeholder with actual package name
dockerfile_content=$(printf '%s' "$dockerfile_content" | sed "s|PLACEHOLDER_OPENCODE_PKG|${opencode_pkg}|g")
printf '%s' "$dockerfile_content"
else
cat <<'EOF'
# Dockerfile.sloptrap
@@ -725,6 +744,24 @@ validate_package_list() {
done
}
# Common package name aliases for user convenience
expand_package_alias() {
local pkg=$1
case "$pkg" in
rg) printf 'ripgrep' ;;
git) printf 'git' ;;
curl) printf 'curl' ;;
bash) printf 'bash' ;;
ca-certificates) printf 'ca-certificates' ;;
libstdc++6) printf 'libstdc++6' ;;
xxd) printf 'xxd' ;;
file) printf 'file' ;;
procps) printf 'procps' ;;
util-linux) printf 'util-linux' ;;
*) printf '%s' "$pkg" ;;
esac
}
detect_container_engine() {
local override=${SLOPTRAP_CONTAINER_ENGINE-}
if [[ -n $override ]]; then
@@ -815,6 +852,26 @@ normalize_wizard_allow_host_network() {
esac
}
parse_opencode_context() {
local raw=$1
local upper=${raw^^}
local number suffix multiplier=1
[[ -n $upper ]] || error "$MANIFEST_PATH: opencode_context must not be empty"
if [[ $upper =~ ^([0-9]+)([KMG]?)$ ]]; then
number=${BASH_REMATCH[1]}
suffix=${BASH_REMATCH[2]}
else
error "$MANIFEST_PATH: opencode_context must be an integer optionally suffixed with K, M, or G (got '$raw')"
fi
case "$suffix" in
"") multiplier=1 ;;
K) multiplier=1024 ;;
M) multiplier=$((1024 * 1024)) ;;
G) multiplier=$((1024 * 1024 * 1024)) ;;
esac
printf '%s' $(( number * multiplier ))
}
run_wizard() {
local manifest_path=$1
if [[ -L $manifest_path ]]; then
@@ -832,13 +889,15 @@ run_wizard() {
local default_allow_host_network
local default_opencode_server
local default_opencode_model
local default_opencode_context
default_name=$(manifest_default_value "name" "$(basename "$CODE_DIR")")
default_packages_extra=$(manifest_default_value "packages_extra" "")
default_agent=$(manifest_default_value "agent" "codex")
default_allow_host_network=$(manifest_default_value "allow_host_network" "false")
default_opencode_server=$(manifest_default_value "opencode_server" "http://localhost:11434")
default_opencode_model=$(manifest_default_value "opencode_model" "llama3")
default_opencode_server=$(manifest_default_value "opencode_server" "http://localhost:8080")
default_opencode_model=$(manifest_default_value "opencode_model" "bartowski/Qwen_Qwen3.5-9B-GGUF:Q8_0")
default_opencode_context=$(manifest_default_value "opencode_context" "256K")
local action="Creating"
if [[ -f $manifest_path ]]; then
@@ -886,22 +945,32 @@ run_wizard() {
# If opencode, prompt for server and model
if [[ "$default_agent" == "opencode" ]]; then
while true; do
info_line "opencode_server: OpenAI-compatible server URL (e.g., http://localhost:11434).\n"
info_line "opencode_server: OpenAI-compatible server URL (e.g., http://localhost:8080).\n"
value=$(prompt_manifest_value "opencode_server" "$default_opencode_server")
value=$(trim "$value")
[[ -n $value ]] || value="http://localhost:11434"
[[ -n $value ]] || value="http://localhost:8080"
default_opencode_server=$value
break
done
while true; do
info_line "opencode_model: Model name on the server (e.g., llama3).\n"
info_line "opencode_model: Model name on the server (e.g., bartowski/Qwen_Qwen3.5-9B-GGUF:Q8_0).\n"
value=$(prompt_manifest_value "opencode_model" "$default_opencode_model")
value=$(trim "$value")
[[ -n $value ]] || value="llama3"
[[ -n $value ]] || value="bartowski/Qwen_Qwen3.5-9B-GGUF:Q8_0"
default_opencode_model=$value
break
done
while true; do
info_line "opencode_context: Context window for the model (e.g., 256K).\n"
value=$(prompt_manifest_value "opencode_context" "$default_opencode_context")
value=$(trim "$value")
[[ -n $value ]] || value="256K"
parse_opencode_context "$value" >/dev/null
default_opencode_context=$value
break
done
fi
while true; do
@@ -924,6 +993,7 @@ EOF
cat >> "$manifest_path" <<EOF
opencode_server=$default_opencode_server
opencode_model=$default_opencode_model
opencode_context=$default_opencode_context
EOF
fi
info_line "Wrote %s\n" "$manifest_path"
@@ -948,7 +1018,10 @@ print_config() {
info_line "backend=%s\n" "opencode"
info_line "opencode_server=%s\n" "$OPENCODE_SERVER"
info_line "opencode_model=%s\n" "$OPENCODE_MODEL"
info_line "opencode_context=%s\n" "$OPENCODE_CONTEXT"
info_line "opencode_state_home=%s\n" "$OPENCODE_STATE_HOME_HOST"
info_line "opencode_config=%s\n" "$OPENCODE_CONFIG_HOST"
info_line "runtime_flags=\n"
else
info_line "backend=%s\n" "codex"
info_line "codex_home=%s\n" "$CODEX_STATE_HOME_HOST"
@@ -959,9 +1032,10 @@ print_config() {
info_line "codex_home_bootstrap=%s\n" "$CODEX_HOME_BOOTSTRAP"
info_line "codex_archive=%s\n" "$SLOPTRAP_CODEX_ARCHIVE"
info_line "codex_url=%s\n" "$SLOPTRAP_CODEX_URL"
info_line "runtime_flags=%s\n" "$CODEX_ARGS_DISPLAY"
fi
info_line "host_alias=%s\n" "${SLOPTRAP_HOST_ALIAS:-}"
info_line "needs_login=%s\n" "$NEED_LOGIN"
info_line "runtime_flags=%s\n" "$CODEX_ARGS_DISPLAY"
info_line "ignore_stub_base=%s\n" "$IGNORE_STUB_BASE"
if [[ ${#SLOPTRAP_IGNORE_ENTRIES[@]} -gt 0 ]]; then
local ignore_paths
@@ -990,10 +1064,16 @@ print_manifest_summary() {
comment_line " backend=%s\n" "opencode"
comment_line " opencode_server=%s\n" "$OPENCODE_SERVER"
comment_line " opencode_model=%s\n" "$OPENCODE_MODEL"
comment_line " opencode_context=%s\n" "$OPENCODE_CONTEXT"
comment_line " opencode_config=%s\n" "$OPENCODE_CONFIG_CONT"
comment_line " runtime_flags=\n"
else
comment_line " backend=%s\n" "codex"
comment_line " runtime_flags=%s\n" "$CODEX_ARGS_DISPLAY"
fi
if [[ -n $SLOPTRAP_HOST_ALIAS ]]; then
comment_line " host_alias=%s\n" "$SLOPTRAP_HOST_ALIAS"
fi
comment_line " runtime_flags=%s\n" "$CODEX_ARGS_DISPLAY"
comment_line " allow_host_network=%s\n" "$ALLOW_HOST_NETWORK"
}
@@ -1019,6 +1099,10 @@ Current resolved sloptrap state:
- network_mode=$network_mode (host when host networking is enabled; otherwise isolated)
EOF
)
if [[ -n $SLOPTRAP_HOST_ALIAS ]]; then
prompt+=$'\n'
prompt+="- host_alias=$SLOPTRAP_HOST_ALIAS (container-side address for services running on the host)\n"
fi
printf '%s' "$prompt"
}
@@ -1063,7 +1147,7 @@ get_env_default() {
validate_codex_archive_name() {
local name=$1
[[ $name =~ ^codex-[A-Za-z0-9_.-]+$ ]] || error "invalid Codex archive name '$name'"
[[ $name =~ ^(codex|opencode)-[A-Za-z0-9_.-]+$ ]] || error "invalid binary name '$name'"
}
detect_codex_archive_name() {
@@ -1186,6 +1270,106 @@ ensure_opencode_storage_paths() {
ensure_codex_directory "$state_root" "sloptrap Opencode namespace"
ensure_codex_directory "$state_bucket" "sloptrap Opencode state root"
ensure_codex_directory "$CODEX_STATE_HOME_HOST" "project Opencode state"
ensure_codex_directory "$OPENCODE_STATE_HOME_HOST" "project Opencode runtime state"
}
ensure_opencode_config() {
local provider_id="llama.cpp"
local provider_name="llama-server (local)"
local base_url="$OPENCODE_SERVER"
local context_limit
context_limit=$(parse_opencode_context "$OPENCODE_CONTEXT")
local model_key="${OPENCODE_MODEL} - ${context_limit}"
local model_ref="${provider_id}/${model_key}"
local config_dir
case "$base_url" in
*/v1|*/v1/|*/v1\?*|*/v1\#*)
;;
*)
base_url="${base_url%/}/v1"
;;
esac
config_dir=$(dirname "$OPENCODE_CONFIG_HOST")
if $DRY_RUN; then
print_command mkdir -p "$config_dir"
print_command env OPENCODE_CONFIG="$OPENCODE_CONFIG_CONT" opencode
return 0
fi
ensure_codex_directory "$config_dir" "project Opencode config"
if ! jq -n \
--arg schema "https://opencode.ai/config.json" \
--arg provider_id "$provider_id" \
--arg provider_name "$provider_name" \
--arg base_url "$base_url" \
--arg model_id "$OPENCODE_MODEL" \
--arg model_key "$model_key" \
--arg model_ref "$model_ref" \
--argjson context_limit "$context_limit" \
'{
"$schema": $schema,
enabled_providers: [$provider_id],
share: "disabled",
autoupdate: false,
provider: {
($provider_id): {
npm: "@ai-sdk/openai-compatible",
name: $provider_name,
options: {
baseURL: $base_url,
timeout: 1800000,
chunkTimeout: 180000
},
models: {
($model_key): {
name: $model_id,
limit: {
context: $context_limit,
output: 32768
},
cost: {
input: 0.0221,
output: 0.1684,
cache_read: 0.0001,
cache_write: 0.0001
},
tool_call: true,
reasoning: true
}
}
}
},
model: $model_ref,
compaction: {
auto: true,
prune: true,
reserved: 16000
},
watcher: {
ignore: [
".git/**",
"node_modules/**",
"dist/**",
".run/**"
]
},
permission: {
read: "allow",
edit: "allow",
glob: "allow",
grep: "allow",
list: "allow",
bash: "allow",
task: "allow",
question: "allow",
webfetch: "allow",
websearch: "allow",
codesearch: "allow",
external_directory: "deny",
doom_loop: "ask"
}
}' >"$OPENCODE_CONFIG_HOST"; then
error "failed to write opencode config '$OPENCODE_CONFIG_HOST'"
fi
}
fetch_latest_codex_digest() {
@@ -1208,42 +1392,43 @@ fetch_latest_codex_digest() {
printf '%s' "$digest_line"
}
detect_opencode_archive_name() {
local os arch opencode_os opencode_arch
os=$(uname -s 2>/dev/null || true)
arch=$(uname -m 2>/dev/null || true)
[[ -n $os ]] || error "failed to detect host OS for opencode download"
[[ -n $arch ]] || error "failed to detect host architecture for opencode download"
case "$os" in
Linux|Darwin) opencode_os="unknown-linux-gnu" ;;
*) error "unsupported host OS '$os' for opencode download" ;;
esac
case "$arch" in
x86_64|amd64) opencode_arch="x86_64" ;;
arm64|aarch64) opencode_arch="arm64" ;;
*) error "unsupported host architecture '$arch' for opencode download" ;;
esac
printf 'opencode-%s-%s' "$opencode_arch" "$opencode_os"
detect_opencode_asset_name() {
local arch
arch=$(uname -m 2>/dev/null || true)
[[ -n $arch ]] || error "failed to detect host architecture for opencode download"
case "$arch" in
x86_64|amd64)
printf 'opencode-linux-x64.tar.gz'
;;
arm64|aarch64)
printf 'opencode-linux-arm64.tar.gz'
;;
*)
error "unsupported host architecture '$arch' for opencode download"
;;
esac
}
fetch_latest_opencode_digest() {
local api_url="https://api.github.com/repos/anomalyco/opencode/releases/latest"
local target_asset="${SLOPTRAP_CODEX_BIN_NAME}.tar.gz"
[[ -n $SLOPTRAP_CODEX_BIN_NAME ]] || error "opencode binary name is not set"
if ! command -v jq >/dev/null 2>&1; then
error "jq is required to verify the opencode binary digest"
fi
local response
if ! response=$(curl -fsSL "$api_url"); then
error "failed to download opencode release metadata from GitHub"
fi
local digest_line
digest_line=$(jq -r --arg name "$target_asset" '.assets[] | select(.name == $name) | .digest' <<<"$response" | head -n 1)
if [[ -z $digest_line || $digest_line == "null" ]]; then
error "failed to resolve opencode digest from GitHub response"
fi
digest_line=${digest_line#sha256:}
printf '%s' "$digest_line"
local api_url="https://api.github.com/repos/anomalyco/opencode/releases/latest"
local target_asset
target_asset=$(detect_opencode_asset_name)
[[ -n $target_asset ]] || error "opencode binary name is not set"
if ! command -v jq >/dev/null 2>&1; then
error "jq is required to verify the opencode binary digest"
fi
local response
if ! response=$(curl -fsSL "$api_url"); then
error "failed to download opencode release metadata from GitHub"
fi
local digest_line
digest_line=$(jq -r --arg name "$target_asset" '.assets[] | select(.name == $name) | .digest' <<<"$response" | head -n 1)
if [[ -z $digest_line || $digest_line == "null" ]]; then
error "failed to resolve opencode digest from GitHub response (looking for $target_asset)"
fi
digest_line=${digest_line#sha256:}
printf '%s' "$digest_line"
}
ensure_codex_binary() {
@@ -1285,28 +1470,30 @@ ensure_opencode_binary() {
if [[ -x $CODEX_BIN_PATH ]]; then
return 0
fi
local tar_transform="s/${SLOPTRAP_CODEX_BIN_NAME}/${SLOPTRAP_CODEX_BIN_NAME}/"
local target_asset
target_asset=$(detect_opencode_asset_name)
local download_dir
download_dir=$(create_temp_dir "opencode")
local tmp_archive="$download_dir/${SLOPTRAP_CODEX_BIN_NAME}.tar.gz"
local tmp_archive="$download_dir/$target_asset"
local opencode_url="https://github.com/anomalyco/opencode/releases/latest/download/${target_asset}"
if $DRY_RUN; then
print_command curl -Lso "$tmp_archive" "https://github.com/anomalyco/opencode/releases/latest/download/${SLOPTRAP_CODEX_BIN_NAME}.tar.gz"
print_command curl -Lso "$tmp_archive" "$opencode_url"
print_command sha256sum -c -
print_command tar -xzf "$tmp_archive" --transform="$tar_transform" -C "$SLOPTRAP_BUILD_CONTEXT"
print_command tar -xzf "$tmp_archive" --transform="s/opencode/$SLOPTRAP_CODEX_BIN_NAME/" -C "$SLOPTRAP_BUILD_CONTEXT"
print_command chmod 0755 "$CODEX_BIN_PATH"
return 0
fi
if ! curl -Lso "$tmp_archive" "https://github.com/anomalyco/opencode/releases/latest/download/${SLOPTRAP_CODEX_BIN_NAME}.tar.gz"; then
if ! curl -Lso "$tmp_archive" "$opencode_url"; then
rm -rf "$download_dir"
error "failed to download opencode binary from https://github.com/anomalyco/opencode/releases/latest/download/${SLOPTRAP_CODEX_BIN_NAME}.tar.gz"
error "failed to download opencode binary from '$opencode_url'"
fi
local expected_digest
expected_digest=$(fetch_latest_opencode_digest)
if ! printf "%s %s\n" "$expected_digest" "$tmp_archive" | sha256sum -c - >/dev/null 2>&1; then
rm -rf "$download_dir" "$CODEX_BIN_PATH"
error "checksum verification failed for ${SLOPTRAP_CODEX_BIN_NAME}"
error "checksum verification failed for $SLOPTRAP_CODEX_BIN_NAME"
fi
if ! tar -xzf "$tmp_archive" --transform="$tar_transform" -C "$SLOPTRAP_BUILD_CONTEXT"; then
if ! tar -xzf "$tmp_archive" --transform="s/opencode/$SLOPTRAP_CODEX_BIN_NAME/" -C "$SLOPTRAP_BUILD_CONTEXT"; then
rm -rf "$download_dir"
error "failed to extract opencode binary"
fi
@@ -1345,7 +1532,13 @@ normalize_package_list() {
[[ -z $raw ]] && return 0
local -a tokens=()
read -r -a tokens <<< "$raw"
printf '%s' "${tokens[*]}"
local -a normalized=()
local token
for token in "${tokens[@]}"; do
# Expand package aliases
normalized+=("$(expand_package_alias "$token")")
done
printf '%s' "${normalized[*]}"
}
targets_need_build() {
@@ -1364,6 +1557,30 @@ targets_need_build() {
return 1
}
normalize_local_server_url() {
local url=$1
local replacement_host=$2
if [[ $url =~ ^([A-Za-z][A-Za-z0-9+.-]*://)(localhost|127\.0\.0\.1|\[::1\])([:/?#].*)?$ ]]; then
printf '%s%s%s' "${BASH_REMATCH[1]}" "$replacement_host" "${BASH_REMATCH[3]}"
return 0
fi
printf '%s' "$url"
}
ensure_host_loopback_network_access() {
if [[ $SLOPTRAP_NETWORK_NAME != slirp4netns* ]]; then
return 0
fi
if [[ $SLOPTRAP_NETWORK_NAME == *allow_host_loopback=* ]]; then
return 0
fi
if [[ $SLOPTRAP_NETWORK_NAME == "slirp4netns" ]]; then
SLOPTRAP_NETWORK_NAME="slirp4netns:allow_host_loopback=true"
else
SLOPTRAP_NETWORK_NAME="${SLOPTRAP_NETWORK_NAME},allow_host_loopback=true"
fi
}
prepare_container_runtime() {
resolve_container_workdir
SLOPTRAP_PACKAGES_EXTRA_RESOLVED=$PACKAGES_EXTRA
@@ -1390,7 +1607,7 @@ prepare_container_runtime() {
SLOPTRAP_DOCKERFILE_SOURCE=""
fi
SLOPTRAP_PACKAGES_BASE=$(get_env_default "SLOPTRAP_PACKAGES" "curl bash ca-certificates libstdc++6 git ripgrep xxd file procps util-linux")
SLOPTRAP_PACKAGES_BASE=$(get_env_default "SLOPTRAP_PACKAGES" "curl bash ca-certificates libstdc++6 wget git ripgrep xxd file procps util-linux")
validate_package_list "SLOPTRAP_PACKAGES" "$SLOPTRAP_PACKAGES_BASE" "SLOPTRAP_PACKAGES"
local default_codex_archive
default_codex_archive=$(detect_codex_archive_name)
@@ -1413,8 +1630,15 @@ prepare_container_runtime() {
SLOPTRAP_CODEX_ARCHIVE=$inferred_archive
fi
fi
SLOPTRAP_CODEX_BIN_NAME=$(get_env_default "SLOPTRAP_CODEX_BIN" "codex")
if [[ "$BACKEND" == "opencode" ]]; then
SLOPTRAP_CODEX_BIN_NAME=$(get_env_default "SLOPTRAP_CODEX_BIN" "opencode")
else
SLOPTRAP_CODEX_BIN_NAME=$(get_env_default "SLOPTRAP_CODEX_BIN" "codex")
fi
SLOPTRAP_CODEX_HOME_CONT=$(get_env_default "SLOPTRAP_CODEX_HOME_CONT" "/codex")
if [[ "$BACKEND" == "opencode" ]]; then
OPENCODE_CONFIG_CONT="$SLOPTRAP_CODEX_HOME_CONT/config/opencode/opencode.json"
fi
SLOPTRAP_CODEX_UID=$(get_env_default "SLOPTRAP_CODEX_UID" "1337")
SLOPTRAP_CODEX_GID=$(get_env_default "SLOPTRAP_CODEX_GID" "1337")
SLOPTRAP_SECURITY_OPTS_EXTRA=$(get_env_default "SLOPTRAP_SECURITY_OPTS_EXTRA" "")
@@ -1432,6 +1656,9 @@ prepare_container_runtime() {
elif [[ $SLOPTRAP_NETWORK_NAME == "host" ]]; then
error "$MANIFEST_PATH: host networking requires allow_host_network=true"
fi
if [[ "$BACKEND" == "opencode" && $ALLOW_HOST_NETWORK == false ]]; then
ensure_host_loopback_network_access
fi
SLOPTRAP_LIMITS_PID=$(get_env_default "SLOPTRAP_LIMITS_PID" "1024")
SLOPTRAP_LIMITS_RAM=$(get_env_default "SLOPTRAP_LIMITS_RAM" "1024m")
SLOPTRAP_LIMITS_SWP=$(get_env_default "SLOPTRAP_LIMITS_SWP" "1024m")
@@ -1510,6 +1737,7 @@ prepare_container_runtime() {
if [[ "$BACKEND" == "opencode" ]]; then
env_args+=(
-e "OPENCODE_HOME=$SLOPTRAP_CODEX_HOME_CONT"
-e "OPENCODE_CONFIG=$OPENCODE_CONFIG_CONT"
-e "OPENCODE_SERVER=$OPENCODE_SERVER"
-e "OPENCODE_MODEL=$OPENCODE_MODEL"
)
@@ -1527,6 +1755,16 @@ prepare_container_runtime() {
env_args+=(-e "SLOPTRAP_HOST_USER=$user")
fi
local -a network_opts=(--network "$SLOPTRAP_NETWORK_NAME")
if [[ $ALLOW_HOST_NETWORK == false ]]; then
SLOPTRAP_HOST_ALIAS="sloptrap.host"
network_opts+=(--add-host "$SLOPTRAP_HOST_ALIAS:host-gateway")
env_args+=(-e "SLOPTRAP_HOST_ALIAS=$SLOPTRAP_HOST_ALIAS")
if [[ "$BACKEND" == "opencode" ]]; then
OPENCODE_SERVER=$(normalize_local_server_url "$OPENCODE_SERVER" "$SLOPTRAP_HOST_ALIAS")
fi
else
SLOPTRAP_HOST_ALIAS=""
fi
local -a user_opts=("--user" "$uid:$gid")
if [[ $CONTAINER_ENGINE == "podman" ]]; then
user_opts=(--userns="keep-id:uid=$uid,gid=$gid" "${user_opts[@]}")
@@ -1560,55 +1798,57 @@ prepare_container_runtime() {
}
build_image() {
if [[ "$BACKEND" == "opencode" ]]; then
ensure_opencode_binary
else
ensure_codex_binary
fi
if [[ $SKIP_BUILD_BANNER != true ]]; then
print_banner
fi
print_manifest_summary
local extra_packages_arg
extra_packages_arg=$(normalize_package_list "$SLOPTRAP_PACKAGES_EXTRA_RESOLVED")
if ! $DRY_RUN; then
status_line "Building %s\n" "$SLOPTRAP_IMAGE_NAME"
fi
local -a cmd=(
"$CONTAINER_ENGINE" build --quiet
-t "$SLOPTRAP_IMAGE_NAME"
-f "$SLOPTRAP_DOCKERFILE_PATH"
--network "$SLOPTRAP_NETWORK_NAME"
--label "$SLOPTRAP_IMAGE_LABEL"
--build-arg "BASE_PACKAGES=$SLOPTRAP_PACKAGES_BASE"
--build-arg "CODEX_BIN=$SLOPTRAP_CODEX_BIN_NAME"
--build-arg "CODEX_UID=$SLOPTRAP_CODEX_UID"
--build-arg "CODEX_GID=$SLOPTRAP_CODEX_GID"
)
if [[ -n $extra_packages_arg ]]; then
cmd+=(--build-arg "EXTRA_PACKAGES=$extra_packages_arg")
fi
cmd+=("$SLOPTRAP_BUILD_CONTEXT")
if $DRY_RUN; then
run_or_print "${cmd[@]}"
return
fi
prepare_build_context
if [[ "$BACKEND" == "opencode" ]]; then
ensure_opencode_binary
else
ensure_codex_binary
fi
if [[ $SKIP_BUILD_BANNER != true ]]; then
print_banner
fi
print_manifest_summary
local extra_packages_arg
extra_packages_arg=$(normalize_package_list "$SLOPTRAP_PACKAGES_EXTRA_RESOLVED")
if ! $DRY_RUN; then
status_line "Building %s\n" "$SLOPTRAP_IMAGE_NAME"
fi
local -a cmd=(
"$CONTAINER_ENGINE" build --quiet
-t "$SLOPTRAP_IMAGE_NAME"
-f "$SLOPTRAP_DOCKERFILE_PATH"
--network "$SLOPTRAP_NETWORK_NAME"
--label "$SLOPTRAP_IMAGE_LABEL"
--build-arg "BASE_PACKAGES=$SLOPTRAP_PACKAGES_BASE"
--build-arg "CODEX_BIN=$SLOPTRAP_CODEX_BIN_NAME"
--build-arg "CODEX_UID=$SLOPTRAP_CODEX_UID"
--build-arg "CODEX_GID=$SLOPTRAP_CODEX_GID"
)
if [[ -n $extra_packages_arg ]]; then
cmd+=(--build-arg "EXTRA_PACKAGES=$extra_packages_arg")
fi
cmd+=("$SLOPTRAP_BUILD_CONTEXT")
if $DRY_RUN; then
run_or_print "${cmd[@]}"
return
fi
local build_output
if ! build_output=$("${cmd[@]}"); then
return 1
fi
build_output=$(trim "$build_output")
if [[ -n $build_output ]]; then
comment_line "Image %s\n" "$build_output"
fi
local build_output
if ! build_output=$("${cmd[@]}"); then
return 1
fi
build_output=$(trim "$build_output")
if [[ -n $build_output ]]; then
comment_line "Image %s\n" "$build_output"
fi
}
rebuild_image() {
if [[ "$BACKEND" == "opencode" ]]; then
ensure_opencode_binary
else
ensure_codex_binary
prepare_build_context
if [[ "$BACKEND" == "opencode" ]]; then
ensure_opencode_binary
else
ensure_codex_binary
fi
if [[ $SKIP_BUILD_BANNER != true ]]; then
print_banner
@@ -1720,15 +1960,14 @@ run_codex_command() {
local -a auth_mount=()
if [[ "$BACKEND" == "opencode" ]]; then
ensure_opencode_storage_paths
ensure_opencode_config
else
ensure_codex_storage_paths
fi
append_auth_mount_arg false auth_mount
local -a cmd=("${BASE_CONTAINER_CMD[@]}" "${auth_mount[@]}" "${source_args[@]}" "$BACKEND")
local -a cmd=("${BASE_CONTAINER_CMD[@]}" "${auth_mount[@]}" "${source_args[@]}")
if [[ "$BACKEND" == "opencode" ]]; then
cmd+=("--server" "$OPENCODE_SERVER")
cmd+=("--model" "$OPENCODE_MODEL")
cmd+=("--sandbox" "workspace-write")
true
else
if [[ ${#CODEX_ARGS_ARRAY[@]} -gt 0 ]]; then
cmd+=("${CODEX_ARGS_ARRAY[@]}")
@@ -1744,9 +1983,13 @@ run_codex() {
if ! $DRY_RUN; then
status_line "Running %s\n" "$SLOPTRAP_IMAGE_NAME"
fi
local runtime_prompt
runtime_prompt=$(build_runtime_context_prompt)
run_codex_command "$runtime_prompt"
if [[ "$BACKEND" == "opencode" ]]; then
run_codex_command
else
local runtime_prompt
runtime_prompt=$(build_runtime_context_prompt)
run_codex_command "$runtime_prompt"
fi
}
run_login_target() {
@@ -1757,19 +2000,23 @@ run_login_target() {
status_line "Login %s\n" "$SLOPTRAP_IMAGE_NAME"
fi
append_auth_mount_arg true auth_mount
local -a cmd=("${BASE_CONTAINER_CMD[@]}" "${auth_mount[@]}" "${source_args[@]}" "codex" login)
local -a cmd=("${BASE_CONTAINER_CMD[@]}" "${auth_mount[@]}" "${source_args[@]}" login)
run_runtime_container_cmd "${cmd[@]}"
}
run_shell_target() {
ensure_codex_storage_paths
if [[ "$BACKEND" == "opencode" ]]; then
ensure_opencode_storage_paths
else
ensure_codex_storage_paths
fi
local -a source_args=("$SLOPTRAP_IMAGE_NAME")
local -a auth_mount=()
if ! $DRY_RUN; then
status_line "Shell %s\n" "$SLOPTRAP_IMAGE_NAME"
fi
append_auth_mount_arg false auth_mount
local -a cmd=("${BASE_CONTAINER_CMD[@]}" "${auth_mount[@]}" "${source_args[@]}" /bin/bash)
local -a cmd=("${BASE_CONTAINER_CMD[@]}" --entrypoint /bin/bash "${auth_mount[@]}" "${source_args[@]}")
run_runtime_container_cmd "${cmd[@]}"
}
@@ -1778,7 +2025,11 @@ run_resume_target() {
if ! $DRY_RUN; then
status_line "Resume %s (%s)\n" "$SLOPTRAP_IMAGE_NAME" "$session_id"
fi
run_codex_command resume "$session_id"
if [[ "$BACKEND" == "opencode" ]]; then
run_codex_command --session "$session_id"
else
run_codex_command resume "$session_id"
fi
}
process_resume_target() {
@@ -1930,10 +2181,6 @@ ensure_ignore_helper_root
IGNORE_STUB_BASE="$IGNORE_HELPER_ROOT/session-${BASHPID:-$$}"
resolve_sloptrap_ignore "$CODE_DIR"
resolve_container_workdir
NEED_LOGIN=false
if [[ ! -s "$CODEX_AUTH_FILE_HOST" ]]; then
NEED_LOGIN=true
fi
TARGETS=("${TARGETS_INPUT[@]}")
if [[ ${#TARGETS[@]} -eq 0 ]]; then
@@ -1942,7 +2189,7 @@ fi
DEFAULT_TARGETS=("${TARGETS[@]}")
PACKAGES_EXTRA=${MANIFEST[packages_extra]-}
PACKAGES_EXTRA=$(normalize_package_list "${MANIFEST[packages_extra]:-}")
if [[ -n ${MANIFEST[allow_host_network]-} ]]; then
case "${MANIFEST[allow_host_network],,}" in
1|true|yes)
@@ -1982,22 +2229,29 @@ select_backend() {
opencode) BACKEND="opencode" ;;
codex|*) BACKEND="codex" ;;
esac
if [[ "$BACKEND" == "opencode" && ! -x "$(command -v opencode)" ]]; then
error "opencode CLI not found; install from https://opencode.ai"
fi
}
select_backend
if [[ "$BACKEND" == "opencode" ]]; then
OPENCODE_SERVER="${MANIFEST[opencode_server]:-http://localhost:11434}"
OPENCODE_MODEL="${MANIFEST[opencode_model]:-llama3}"
OPENCODE_SERVER="${MANIFEST[opencode_server]:-http://localhost:8080}"
OPENCODE_MODEL="${MANIFEST[opencode_model]:-bartowski/Qwen_Qwen3.5-9B-GGUF:Q8_0}"
OPENCODE_CONTEXT="${MANIFEST[opencode_context]:-256K}"
OPENCODE_STATE_HOME_HOST="$CODEX_STATE_HOME_HOST/opencode"
OPENCODE_CONFIG_HOST="$CODEX_STATE_HOME_HOST/config/opencode/opencode.json"
OPENCODE_CONFIG_CONT=""
else
OPENCODE_SERVER=""
OPENCODE_MODEL=""
OPENCODE_CONTEXT=""
OPENCODE_STATE_HOME_HOST=""
OPENCODE_CONFIG_HOST=""
OPENCODE_CONFIG_CONT=""
fi
NEED_LOGIN=false
if [[ "$BACKEND" != "opencode" && ! -s "$CODEX_AUTH_FILE_HOST" ]]; then
NEED_LOGIN=true
fi
CONTAINER_ENGINE="$(detect_container_engine)"