agent-control-plane 0.7.0 → 0.7.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +5 -3
- package/tools/bin/adapter-capabilities.sh +84 -0
- package/tools/bin/adapter-interface.sh +97 -0
- package/tools/bin/claude-adapter.sh +73 -0
- package/tools/bin/codex-adapter.sh +123 -0
- package/tools/bin/kilo-adapter.sh +108 -0
- package/tools/bin/ollama-adapter.sh +160 -0
- package/tools/bin/openclaw-adapter.sh +69 -0
- package/tools/bin/opencode-adapter.sh +98 -0
- package/tools/bin/pi-adapter.sh +95 -0
- package/tools/bin/run-with-adapter.sh +34 -0
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "agent-control-plane",
|
|
3
|
-
"version": "0.7.
|
|
3
|
+
"version": "0.7.1",
|
|
4
4
|
"description": "Help a repo keep GitHub-driven coding agents running reliably without constant human babysitting",
|
|
5
5
|
"homepage": "https://github.com/ducminhnguyen0319/agent-control-plane",
|
|
6
6
|
"bugs": {
|
|
@@ -57,6 +57,9 @@
|
|
|
57
57
|
"tools/bin/github-*.sh",
|
|
58
58
|
"tools/bin/profile-*.sh",
|
|
59
59
|
"tools/bin/test-smoke.sh",
|
|
60
|
+
"tools/bin/*-adapter.sh",
|
|
61
|
+
"tools/bin/adapter-*.sh",
|
|
62
|
+
"tools/bin/run-with-adapter.sh",
|
|
60
63
|
"tools/dashboard/",
|
|
61
64
|
"tools/templates/issue-prompt-template.md",
|
|
62
65
|
"tools/templates/pr-fix-template.md",
|
|
@@ -69,8 +72,7 @@
|
|
|
69
72
|
"tools/vendor/codex-quota-manager/scripts"
|
|
70
73
|
],
|
|
71
74
|
"publishConfig": {
|
|
72
|
-
"access": "public"
|
|
73
|
-
"provenance": true
|
|
75
|
+
"access": "public"
|
|
74
76
|
},
|
|
75
77
|
"engines": {
|
|
76
78
|
"node": ">=18"
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# adapter-capabilities.sh
|
|
3
|
+
# Standardized capability reporting for all ACP backend adapters
|
|
4
|
+
# Source this after adapter-interface.sh
|
|
5
|
+
|
|
6
|
+
set -euo pipefail
|
|
7
|
+
|
|
8
|
+
# Default capabilities (override in adapter)
|
|
9
|
+
ADAPTER_CAP_CONTEXT_WINDOW=0
|
|
10
|
+
ADAPTER_CAP_STREAMING=false
|
|
11
|
+
ADAPTER_CAP_TOOLS_SUPPORT=false
|
|
12
|
+
ADAPTER_CAP_LOCAL_MODEL=false
|
|
13
|
+
ADAPTER_CAP_CLOUD_API=false
|
|
14
|
+
ADAPTER_CAP_RESIDENT_MODE=false
|
|
15
|
+
ADAPTER_CAP_JSON_OUTPUT=false
|
|
16
|
+
ADAPTER_CAP_MAX_TIMEOUT=3600
|
|
17
|
+
|
|
18
|
+
# Print adapter capabilities as key=value pairs
|
|
19
|
+
adapter_capabilities() {
|
|
20
|
+
cat <<EOF
|
|
21
|
+
id=${ADAPTER_ID}
|
|
22
|
+
name=${ADAPTER_NAME}
|
|
23
|
+
type=${ADAPTER_TYPE}
|
|
24
|
+
version=${ADAPTER_VERSION}
|
|
25
|
+
model=${ADAPTER_MODEL:-}
|
|
26
|
+
base_url=${ADAPTER_BASE_URL:-}
|
|
27
|
+
context_window=${ADAPTER_CAP_CONTEXT_WINDOW}
|
|
28
|
+
streaming=${ADAPTER_CAP_STREAMING}
|
|
29
|
+
tools_support=${ADAPTER_CAP_TOOLS_SUPPORT}
|
|
30
|
+
local_model=${ADAPTER_CAP_LOCAL_MODEL}
|
|
31
|
+
cloud_api=${ADAPTER_CAP_CLOUD_API}
|
|
32
|
+
resident_mode=${ADAPTER_CAP_RESIDENT_MODE}
|
|
33
|
+
json_output=${ADAPTER_CAP_JSON_OUTPUT}
|
|
34
|
+
max_timeout=${ADAPTER_CAP_MAX_TIMEOUT}
|
|
35
|
+
EOF
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
# Check if adapter supports a specific capability
|
|
39
|
+
# Usage: adapter_supports CAPABILITY_NAME
|
|
40
|
+
adapter_supports() {
|
|
41
|
+
local cap="$(echo "$1" | tr '[:lower:]' '[:upper:]')"
|
|
42
|
+
local value
|
|
43
|
+
case "$cap" in
|
|
44
|
+
STREAMING) value="${ADAPTER_CAP_STREAMING}" ;;
|
|
45
|
+
TOOLS) value="${ADAPTER_CAP_TOOLS_SUPPORT}" ;;
|
|
46
|
+
LOCAL) value="${ADAPTER_CAP_LOCAL_MODEL}" ;;
|
|
47
|
+
CLOUD) value="${ADAPTER_CAP_CLOUD_API}" ;;
|
|
48
|
+
RESIDENT) value="${ADAPTER_CAP_RESIDENT_MODE}" ;;
|
|
49
|
+
JSON) value="${ADAPTER_CAP_JSON_OUTPUT}" ;;
|
|
50
|
+
*)
|
|
51
|
+
echo "UNKNOWN_CAPABILITY: $1"
|
|
52
|
+
return 1
|
|
53
|
+
;;
|
|
54
|
+
esac
|
|
55
|
+
[[ "$value" == "true" ]] && return 0
|
|
56
|
+
return 1
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
# Validate adapter implements required functions
|
|
60
|
+
adapter_validate_interface() {
|
|
61
|
+
local errors=0
|
|
62
|
+
for func in adapter_info adapter_health_check adapter_run adapter_status; do
|
|
63
|
+
if ! declare -f "$func" >/dev/null 2>&1; then
|
|
64
|
+
echo "ERROR: $func() not implemented in ${ADAPTER_ID} adapter"
|
|
65
|
+
errors=$((errors + 1))
|
|
66
|
+
fi
|
|
67
|
+
done
|
|
68
|
+
[[ $errors -eq 0 ]] && return 0
|
|
69
|
+
return 1
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
# Enhanced health check that also reports capabilities
|
|
73
|
+
adapter_health_check_with_capabilities() {
|
|
74
|
+
local health_output
|
|
75
|
+
if ! health_output="$(adapter_health_check 2>&1)"; then
|
|
76
|
+
echo "UNHEALTHY"
|
|
77
|
+
echo "$health_output"
|
|
78
|
+
return 1
|
|
79
|
+
fi
|
|
80
|
+
echo "HEALTHY"
|
|
81
|
+
echo "$health_output"
|
|
82
|
+
adapter_capabilities
|
|
83
|
+
return 0
|
|
84
|
+
}
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# adapter-interface.sh
|
|
3
|
+
# Standard interface for ACP backend adapters
|
|
4
|
+
# All adapters must implement these functions:
|
|
5
|
+
#
|
|
6
|
+
# adapter_info() - Print adapter metadata (id, name, type, version)
|
|
7
|
+
# adapter_health_check() - Check if backend is available (exit 0 = healthy)
|
|
8
|
+
# adapter_run() - Execute a task (params: MODE SESSION WORKTREE PROMPT_FILE)
|
|
9
|
+
# adapter_status() - Get run status (params: RUNS_ROOT SESSION)
|
|
10
|
+
#
|
|
11
|
+
# Usage: source this file in adapter implementations
|
|
12
|
+
|
|
13
|
+
set -euo pipefail
|
|
14
|
+
|
|
15
|
+
# Default adapter metadata (override in adapter script)
|
|
16
|
+
ADAPTER_ID="${ADAPTER_ID:-unknown}"
|
|
17
|
+
ADAPTER_NAME="${ADAPTER_NAME:-Unknown Adapter}"
|
|
18
|
+
ADAPTER_TYPE="${ADAPTER_TYPE:-unknown}" # coding, local-model, cloud-api
|
|
19
|
+
ADAPTER_VERSION="${ADAPTER_VERSION:-0.0.1}"
|
|
20
|
+
|
|
21
|
+
# Print adapter metadata as key=value pairs
|
|
22
|
+
adapter_info() {
|
|
23
|
+
cat <<EOF
|
|
24
|
+
id=${ADAPTER_ID}
|
|
25
|
+
name=${ADAPTER_NAME}
|
|
26
|
+
type=${ADAPTER_TYPE}
|
|
27
|
+
version=${ADAPTER_VERSION}
|
|
28
|
+
model=${ADAPTER_MODEL:-}
|
|
29
|
+
base_url=${ADAPTER_BASE_URL:-}
|
|
30
|
+
EOF
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
# Default health check (override in adapter)
|
|
34
|
+
# Returns: 0 = healthy, 1 = unhealthy
|
|
35
|
+
adapter_health_check() {
|
|
36
|
+
echo "WARN: adapter_health_check() not implemented for ${ADAPTER_ID}"
|
|
37
|
+
return 0
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
# Default run function (MUST override in adapter)
|
|
41
|
+
adapter_run() {
|
|
42
|
+
echo "ERROR: adapter_run() not implemented for ${ADAPTER_ID}"
|
|
43
|
+
return 1
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
# Default status function (override in adapter if needed)
|
|
47
|
+
adapter_status() {
|
|
48
|
+
local runs_root="${1:?usage: adapter_status RUNS_ROOT SESSION}"
|
|
49
|
+
local session="${2:?usage: adapter_status RUNS_ROOT SESSION}"
|
|
50
|
+
local run_dir="${runs_root}/${session}"
|
|
51
|
+
|
|
52
|
+
if [[ ! -d "$run_dir" ]]; then
|
|
53
|
+
echo "NOT_FOUND"
|
|
54
|
+
return 1
|
|
55
|
+
fi
|
|
56
|
+
|
|
57
|
+
if [[ -f "$run_dir/result.env" ]]; then
|
|
58
|
+
source "$run_dir/result.env"
|
|
59
|
+
echo "${OUTCOME:-UNKNOWN}"
|
|
60
|
+
return 0
|
|
61
|
+
fi
|
|
62
|
+
|
|
63
|
+
if [[ -f "$run_dir/runner.env" ]]; then
|
|
64
|
+
source "$run_dir/runner.env"
|
|
65
|
+
echo "${RUNNER_STATE:-RUNNING}"
|
|
66
|
+
return 0
|
|
67
|
+
fi
|
|
68
|
+
|
|
69
|
+
echo "RUNNING"
|
|
70
|
+
return 0
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
# Load adapter implementation
|
|
74
|
+
# Usage: adapter_load IMPLEMENTATION_SCRIPT
|
|
75
|
+
adapter_load() {
|
|
76
|
+
local impl="${1:?usage: adapter_load IMPLEMENTATION_SCRIPT}"
|
|
77
|
+
if [[ ! -f "$impl" ]]; then
|
|
78
|
+
echo "ERROR: Adapter implementation not found: $impl"
|
|
79
|
+
return 1
|
|
80
|
+
fi
|
|
81
|
+
source "$impl"
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
# Validate adapter implements required functions
|
|
85
|
+
adapter_validate() {
|
|
86
|
+
local missing=()
|
|
87
|
+
type adapter_info >/dev/null 2>&1 || missing+=("adapter_info")
|
|
88
|
+
type adapter_health_check >/dev/null 2>&1 || missing+=("adapter_health_check")
|
|
89
|
+
type adapter_run >/dev/null 2>&1 || missing+=("adapter_run")
|
|
90
|
+
|
|
91
|
+
if [[ ${#missing[@]} -gt 0 ]]; then
|
|
92
|
+
echo "ERROR: Adapter ${ADAPTER_ID} missing required functions: ${missing[*]}"
|
|
93
|
+
return 1
|
|
94
|
+
fi
|
|
95
|
+
|
|
96
|
+
return 0
|
|
97
|
+
}
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# claude-adapter.sh
|
|
3
|
+
# Adapter implementation for Claude CLI
|
|
4
|
+
|
|
5
|
+
set -euo pipefail
|
|
6
|
+
|
|
7
|
+
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
|
8
|
+
source "${SCRIPT_DIR}/adapter-interface.sh"
|
|
9
|
+
source "${SCRIPT_DIR}/adapter-capabilities.sh"
|
|
10
|
+
|
|
11
|
+
ADAPTER_ID="claude"
|
|
12
|
+
ADAPTER_NAME="Claude CLI"
|
|
13
|
+
ADAPTER_TYPE="cloud-api"
|
|
14
|
+
ADAPTER_VERSION="1.0.0"
|
|
15
|
+
ADAPTER_MODEL="${CLAUDE_MODEL:-sonnet}"
|
|
16
|
+
|
|
17
|
+
# Claude capabilities
|
|
18
|
+
ADAPTER_CAP_CLOUD_API=true
|
|
19
|
+
ADAPTER_CAP_STREAMING=true
|
|
20
|
+
ADAPTER_CAP_JSON_OUTPUT=true
|
|
21
|
+
ADAPTER_CAP_MAX_TIMEOUT=900
|
|
22
|
+
|
|
23
|
+
adapter_info() {
|
|
24
|
+
cat <<EOF
|
|
25
|
+
id=${ADAPTER_ID}
|
|
26
|
+
name=${ADAPTER_NAME}
|
|
27
|
+
type=${ADAPTER_TYPE}
|
|
28
|
+
version=${ADAPTER_VERSION}
|
|
29
|
+
model=${ADAPTER_MODEL}
|
|
30
|
+
EOF
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
adapter_health_check() {
|
|
34
|
+
if ! command -v claude >/dev/null 2>&1; then
|
|
35
|
+
echo "ERROR: claude CLI not found in PATH"
|
|
36
|
+
return 1
|
|
37
|
+
fi
|
|
38
|
+
echo "OK: Claude adapter healthy"
|
|
39
|
+
return 0
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
adapter_run() {
|
|
43
|
+
local mode="${1:?usage: adapter_run MODE SESSION WORKTREE PROMPT_FILE}"
|
|
44
|
+
local session="${2:?usage: adapter_run MODE SESSION WORKTREE PROMPT_FILE}"
|
|
45
|
+
local worktree="${3:?usage: adapter_run MODE SESSION WORKTREE PROMPT_FILE}"
|
|
46
|
+
local prompt_file="${4:?usage: adapter_run MODE SESSION WORKTREE PROMPT_FILE}"
|
|
47
|
+
|
|
48
|
+
local permission_mode="${CLAUDE_PERMISSION_MODE:-acceptEdits}"
|
|
49
|
+
local timeout_seconds="${CLAUDE_TIMEOUT_SECONDS:-900}"
|
|
50
|
+
|
|
51
|
+
echo "Claude adapter: Running session ${session}"
|
|
52
|
+
|
|
53
|
+
cd "${worktree}" || return 1
|
|
54
|
+
|
|
55
|
+
prompt="$(cat "${prompt_file}")"
|
|
56
|
+
|
|
57
|
+
if ! timeout "${timeout_seconds}" claude \
|
|
58
|
+
--permission-mode "${permission_mode}" \
|
|
59
|
+
--model "${ADAPTER_MODEL}" \
|
|
60
|
+
--print \
|
|
61
|
+
"${prompt}" 2>&1; then
|
|
62
|
+
echo "ERROR: Claude run failed"
|
|
63
|
+
return 1
|
|
64
|
+
fi
|
|
65
|
+
|
|
66
|
+
return 0
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
if [[ "${BASH_SOURCE[0]}" == "${0}" ]]; then
|
|
70
|
+
adapter_info
|
|
71
|
+
echo "---"
|
|
72
|
+
adapter_health_check
|
|
73
|
+
fi
|
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# codex-adapter.sh
|
|
3
|
+
# Adapter implementation for Codex (Claude CLI)
|
|
4
|
+
# Implements: adapter-interface.sh
|
|
5
|
+
|
|
6
|
+
set -euo pipefail
|
|
7
|
+
|
|
8
|
+
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
|
9
|
+
source "${SCRIPT_DIR}/adapter-interface.sh"
|
|
10
|
+
source "${SCRIPT_DIR}/adapter-capabilities.sh"
|
|
11
|
+
|
|
12
|
+
# Codex adapter metadata
|
|
13
|
+
ADAPTER_ID="codex"
|
|
14
|
+
ADAPTER_NAME="Codex (Claude CLI)"
|
|
15
|
+
ADAPTER_TYPE="cloud-api"
|
|
16
|
+
ADAPTER_VERSION="1.0.0"
|
|
17
|
+
ADAPTER_MODEL="${CODEX_MODEL:-sonnet}"
|
|
18
|
+
ADAPTER_BASE_URL=""
|
|
19
|
+
|
|
20
|
+
# Codex capabilities
|
|
21
|
+
ADAPTER_CAP_CLOUD_API=true
|
|
22
|
+
ADAPTER_CAP_STREAMING=true
|
|
23
|
+
ADAPTER_CAP_JSON_OUTPUT=true
|
|
24
|
+
ADAPTER_CAP_MAX_TIMEOUT=900
|
|
25
|
+
|
|
26
|
+
# Print adapter info
|
|
27
|
+
adapter_info() {
|
|
28
|
+
cat <<EOF
|
|
29
|
+
id=${ADAPTER_ID}
|
|
30
|
+
name=${ADAPTER_NAME}
|
|
31
|
+
type=${ADAPTER_TYPE}
|
|
32
|
+
version=${ADAPTER_VERSION}
|
|
33
|
+
model=${ADAPTER_MODEL}
|
|
34
|
+
base_url=${ADAPTER_BASE_URL}
|
|
35
|
+
EOF
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
# Health check: verify claude CLI is available
|
|
39
|
+
adapter_health_check() {
|
|
40
|
+
if ! command -v claude >/dev/null 2>&1; then
|
|
41
|
+
echo "ERROR: claude CLI not found in PATH"
|
|
42
|
+
return 1
|
|
43
|
+
fi
|
|
44
|
+
|
|
45
|
+
# Check if API key is set
|
|
46
|
+
if [[ -z "${ANTHROPIC_API_KEY:-}" && -z "${OPENROUTER_API_KEY:-}" ]]; then
|
|
47
|
+
echo "WARN: No ANTHROPIC_API_KEY or OPENROUTER_API_KEY found"
|
|
48
|
+
# Don't fail - user might use OAuth
|
|
49
|
+
fi
|
|
50
|
+
|
|
51
|
+
echo "OK: Codex adapter healthy (claude CLI available)"
|
|
52
|
+
return 0
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
# Run a task using codex (claude CLI)
|
|
56
|
+
adapter_run() {
|
|
57
|
+
local mode="${1:?usage: adapter_run MODE SESSION WORKTREE PROMPT_FILE}"
|
|
58
|
+
local session="${2:?usage: adapter_run MODE SESSION WORKTREE PROMPT_FILE}"
|
|
59
|
+
local worktree="${3:?usage: adapter_run MODE SESSION WORKTREE PROMPT_FILE}"
|
|
60
|
+
local prompt_file="${4:?usage: adapter_run MODE SESSION WORKTREE PROMPT_FILE}"
|
|
61
|
+
|
|
62
|
+
local permission_mode="${CLAUDE_PERMISSION_MODE:-acceptEdits}"
|
|
63
|
+
local timeout_seconds="${CLAUDE_TIMEOUT_SECONDS:-900}"
|
|
64
|
+
local max_attempts="${CLAUDE_MAX_ATTEMPTS:-3}"
|
|
65
|
+
|
|
66
|
+
echo "Codex adapter: Running session ${session} with model ${ADAPTER_MODEL}"
|
|
67
|
+
|
|
68
|
+
# Read the prompt
|
|
69
|
+
local prompt
|
|
70
|
+
prompt="$(cat "${prompt_file}")"
|
|
71
|
+
|
|
72
|
+
# Change to worktree
|
|
73
|
+
cd "${worktree}" || return 1
|
|
74
|
+
|
|
75
|
+
# Run claude with the prompt
|
|
76
|
+
if ! timeout "${timeout_seconds}" claude \
|
|
77
|
+
--permission-mode "${permission_mode}" \
|
|
78
|
+
--model "${ADAPTER_MODEL}" \
|
|
79
|
+
--print \
|
|
80
|
+
"${prompt}" 2>&1; then
|
|
81
|
+
echo "ERROR: Codex run failed or timed out after ${timeout_seconds}s"
|
|
82
|
+
return 1
|
|
83
|
+
fi
|
|
84
|
+
|
|
85
|
+
echo "Codex adapter: Session ${session} completed"
|
|
86
|
+
return 0
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
# Status check
|
|
90
|
+
adapter_status() {
|
|
91
|
+
local runs_root="${1:?usage: adapter_status RUNS_ROOT SESSION}"
|
|
92
|
+
local session="${2:?usage: adapter_status RUNS_ROOT SESSION}"
|
|
93
|
+
local run_dir="${runs_root}/${session}"
|
|
94
|
+
|
|
95
|
+
if [[ ! -d "$run_dir" ]]; then
|
|
96
|
+
echo "NOT_FOUND"
|
|
97
|
+
return 1
|
|
98
|
+
fi
|
|
99
|
+
|
|
100
|
+
# Check for result file
|
|
101
|
+
if [[ -f "$run_dir/result.env" ]]; then
|
|
102
|
+
source "$run_dir/result.env"
|
|
103
|
+
echo "${OUTCOME:-UNKNOWN}"
|
|
104
|
+
return 0
|
|
105
|
+
fi
|
|
106
|
+
|
|
107
|
+
# Check if claude process is running
|
|
108
|
+
if pgrep -f "claude.*${session}" >/dev/null 2>&1; then
|
|
109
|
+
echo "RUNNING"
|
|
110
|
+
return 0
|
|
111
|
+
fi
|
|
112
|
+
|
|
113
|
+
echo "UNKNOWN"
|
|
114
|
+
return 0
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
# Self-register: validate this adapter implements required functions
|
|
118
|
+
if [[ "${BASH_SOURCE[0]}" == "${0}" ]]; then
|
|
119
|
+
# Running directly - print info
|
|
120
|
+
adapter_info
|
|
121
|
+
echo "---"
|
|
122
|
+
adapter_health_check
|
|
123
|
+
fi
|
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# kilo-adapter.sh
|
|
3
|
+
# Adapter implementation for Kilo Code
|
|
4
|
+
|
|
5
|
+
set -euo pipefail
|
|
6
|
+
|
|
7
|
+
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
|
8
|
+
source "${SCRIPT_DIR}/adapter-interface.sh"
|
|
9
|
+
source "${SCRIPT_DIR}/adapter-capabilities.sh"
|
|
10
|
+
|
|
11
|
+
ADAPTER_ID="kilo"
|
|
12
|
+
ADAPTER_NAME="Kilo Code"
|
|
13
|
+
ADAPTER_TYPE="cloud-api"
|
|
14
|
+
ADAPTER_VERSION="1.0.0"
|
|
15
|
+
ADAPTER_MODEL="${KILO_MODEL:-anthropic/claude-sonnet-4-20250514}"
|
|
16
|
+
|
|
17
|
+
# Kilo capabilities
|
|
18
|
+
ADAPTER_CAP_CLOUD_API=true
|
|
19
|
+
ADAPTER_CAP_STREAMING=true
|
|
20
|
+
ADAPTER_CAP_JSON_OUTPUT=true
|
|
21
|
+
ADAPTER_CAP_MAX_TIMEOUT=900
|
|
22
|
+
|
|
23
|
+
adapter_info() {
|
|
24
|
+
cat <<EOF
|
|
25
|
+
id=${ADAPTER_ID}
|
|
26
|
+
name=${ADAPTER_NAME}
|
|
27
|
+
type=${ADAPTER_TYPE}
|
|
28
|
+
version=${ADAPTER_VERSION}
|
|
29
|
+
model=${ADAPTER_MODEL}
|
|
30
|
+
EOF
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
adapter_health_check() {
|
|
34
|
+
if ! command -v kilo >/dev/null 2>&1; then
|
|
35
|
+
echo "ERROR: kilo CLI not found in PATH"
|
|
36
|
+
return 1
|
|
37
|
+
fi
|
|
38
|
+
|
|
39
|
+
# Verify kilo can actually run (version check)
|
|
40
|
+
if ! kilo --version >/dev/null 2>&1; then
|
|
41
|
+
echo "ERROR: kilo CLI cannot run (check installation)"
|
|
42
|
+
return 1
|
|
43
|
+
fi
|
|
44
|
+
|
|
45
|
+
local version
|
|
46
|
+
version="$(kilo --version 2>/dev/null || true)"
|
|
47
|
+
if [[ -z "$version" ]]; then
|
|
48
|
+
echo "WARN: Could not detect kilo version"
|
|
49
|
+
else
|
|
50
|
+
echo "INFO: Kilo version: $version"
|
|
51
|
+
fi
|
|
52
|
+
|
|
53
|
+
# Verify model is specified
|
|
54
|
+
if [[ -z "${ADAPTER_MODEL}" ]]; then
|
|
55
|
+
echo "WARN: No model specified for Kilo adapter"
|
|
56
|
+
fi
|
|
57
|
+
|
|
58
|
+
echo "OK: Kilo adapter healthy (model: ${ADAPTER_MODEL})"
|
|
59
|
+
return 0
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
adapter_run() {
|
|
63
|
+
local mode="${1:?usage: adapter_run MODE SESSION WORKTREE PROMPT_FILE}"
|
|
64
|
+
local session="${2:?usage: adapter_run MODE SESSION WORKTREE PROMPT_FILE}"
|
|
65
|
+
local worktree="${3:?usage: adapter_run MODE SESSION WORKTREE PROMPT_FILE}"
|
|
66
|
+
local prompt_file="${4:?usage: adapter_run MODE SESSION WORKTREE PROMPT_FILE}"
|
|
67
|
+
|
|
68
|
+
# Validate prompt file
|
|
69
|
+
if [[ ! -f "${prompt_file}" ]]; then
|
|
70
|
+
echo "ERROR: Prompt file not found: ${prompt_file}"
|
|
71
|
+
return 1
|
|
72
|
+
fi
|
|
73
|
+
if [[ ! -s "${prompt_file}" ]]; then
|
|
74
|
+
echo "ERROR: Prompt file is empty: ${prompt_file}"
|
|
75
|
+
return 1
|
|
76
|
+
fi
|
|
77
|
+
|
|
78
|
+
local timeout_seconds="${KILO_TIMEOUT_SECONDS:-900}"
|
|
79
|
+
|
|
80
|
+
echo "Kilo adapter: Running session ${session} with model ${ADAPTER_MODEL}"
|
|
81
|
+
|
|
82
|
+
cd "${worktree}" || return 1
|
|
83
|
+
|
|
84
|
+
prompt="$(cat "${prompt_file}")"
|
|
85
|
+
|
|
86
|
+
# Run kilo and capture output
|
|
87
|
+
local output
|
|
88
|
+
if ! output="$(timeout "${timeout_seconds}" kilo --model "${ADAPTER_MODEL}" "${prompt}" 2>&1)"; then
|
|
89
|
+
echo "ERROR: Kilo run failed or timed out after ${timeout_seconds}s"
|
|
90
|
+
return 1
|
|
91
|
+
fi
|
|
92
|
+
|
|
93
|
+
# Validate JSON stream output (kilo outputs JSON events)
|
|
94
|
+
if ! echo "$output" | python3 -c "import sys, json; [json.loads(line) for line in sys.stdin if line.strip()]" 2>/dev/null; then
|
|
95
|
+
echo "WARN: Kilo output is not valid JSON stream"
|
|
96
|
+
else
|
|
97
|
+
echo "INFO: Kilo output validated as JSON stream"
|
|
98
|
+
fi
|
|
99
|
+
|
|
100
|
+
echo "Kilo adapter: Session ${session} completed"
|
|
101
|
+
return 0
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
if [[ "${BASH_SOURCE[0]}" == "${0}" ]]; then
|
|
105
|
+
adapter_info
|
|
106
|
+
echo "---"
|
|
107
|
+
adapter_health_check
|
|
108
|
+
fi
|
|
@@ -0,0 +1,160 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# ollama-adapter.sh
|
|
3
|
+
# Adapter implementation for Ollama local models
|
|
4
|
+
# Implements: adapter-interface.sh
|
|
5
|
+
|
|
6
|
+
set -euo pipefail
|
|
7
|
+
|
|
8
|
+
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
|
9
|
+
source "${SCRIPT_DIR}/adapter-interface.sh"
|
|
10
|
+
source "${SCRIPT_DIR}/adapter-capabilities.sh"
|
|
11
|
+
|
|
12
|
+
# Ollama adapter metadata
|
|
13
|
+
ADAPTER_ID="ollama"
|
|
14
|
+
ADAPTER_NAME="Ollama Local Models"
|
|
15
|
+
ADAPTER_TYPE="local-model"
|
|
16
|
+
ADAPTER_VERSION="1.0.0"
|
|
17
|
+
ADAPTER_MODEL="${OLLAMA_MODEL:-qwen2.5-coder:7b}"
|
|
18
|
+
ADAPTER_BASE_URL="${OLLAMA_BASE_URL:-http://localhost:11434}"
|
|
19
|
+
|
|
20
|
+
# Ollama capabilities
|
|
21
|
+
ADAPTER_CAP_LOCAL_MODEL=true
|
|
22
|
+
ADAPTER_CAP_STREAMING=true
|
|
23
|
+
ADAPTER_CAP_TOOLS_SUPPORT=true
|
|
24
|
+
ADAPTER_CAP_CONTEXT_WINDOW=32768 # Default, will be detected dynamically
|
|
25
|
+
ADAPTER_CAP_MAX_TIMEOUT=3600
|
|
26
|
+
|
|
27
|
+
# Print adapter info
|
|
28
|
+
adapter_info() {
|
|
29
|
+
cat <<EOF
|
|
30
|
+
id=${ADAPTER_ID}
|
|
31
|
+
name=${ADAPTER_NAME}
|
|
32
|
+
type=${ADAPTER_TYPE}
|
|
33
|
+
version=${ADAPTER_VERSION}
|
|
34
|
+
model=${ADAPTER_MODEL}
|
|
35
|
+
base_url=${ADAPTER_BASE_URL}
|
|
36
|
+
EOF
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
# Health check: verify ollama is running and model is available
|
|
40
|
+
adapter_health_check() {
|
|
41
|
+
# Check if ollama is running
|
|
42
|
+
if ! curl -sf "${ADAPTER_BASE_URL}/api/tags" >/dev/null 2>&1; then
|
|
43
|
+
echo "ERROR: Ollama not reachable at ${ADAPTER_BASE_URL}"
|
|
44
|
+
return 1
|
|
45
|
+
fi
|
|
46
|
+
|
|
47
|
+
# Check if model is available (try to pull if not)
|
|
48
|
+
if ! ollama list 2>/dev/null | grep -q "${ADAPTER_MODEL}"; then
|
|
49
|
+
echo "WARN: Model ${ADAPTER_MODEL} not found locally. Attempting pull..."
|
|
50
|
+
if ! ollama pull "${ADAPTER_MODEL}" 2>&1; then
|
|
51
|
+
echo "ERROR: Failed to pull model ${ADAPTER_MODEL}"
|
|
52
|
+
return 1
|
|
53
|
+
fi
|
|
54
|
+
fi
|
|
55
|
+
|
|
56
|
+
# Detect context window and update capability dynamically
|
|
57
|
+
local context_window
|
|
58
|
+
# Ollama API returns context_length inside model_info with architecture prefix
|
|
59
|
+
# e.g., qwen2.context_length, llama.context_length, etc.
|
|
60
|
+
if command -v jq &>/dev/null; then
|
|
61
|
+
context_window="$(curl -sf "${ADAPTER_BASE_URL}/api/show" -d "{\"name\":\"${ADAPTER_MODEL}\"}" 2>/dev/null | jq -r '.model_info // {} | to_entries[] | select(.key | endswith("context_length")) | .value' 2>/dev/null | head -1 || true)"
|
|
62
|
+
else
|
|
63
|
+
# Fallback: use grep for common patterns
|
|
64
|
+
context_window="$(curl -sf "${ADAPTER_BASE_URL}/api/show" -d "{\"name\":\"${ADAPTER_MODEL}\"}" 2>/dev/null | grep -o '"[a-z]*\.context_length":[0-9]*' | head -1 | grep -o '[0-9]*$' || true)"
|
|
65
|
+
fi
|
|
66
|
+
if [[ -n "$context_window" ]]; then
|
|
67
|
+
ADAPTER_CAP_CONTEXT_WINDOW="$context_window"
|
|
68
|
+
echo "INFO: Detected context window: $context_window tokens"
|
|
69
|
+
else
|
|
70
|
+
echo "WARN: Could not detect context window from Ollama API"
|
|
71
|
+
fi
|
|
72
|
+
|
|
73
|
+
echo "OK: Ollama healthy, model ${ADAPTER_MODEL} available"
|
|
74
|
+
return 0
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
# Run a task using ollama
|
|
78
|
+
adapter_run() {
|
|
79
|
+
local mode="${1:?usage: adapter_run MODE SESSION WORKTREE PROMPT_FILE}"
|
|
80
|
+
local session="${2:?usage: adapter_run MODE SESSION WORKTREE PROMPT_FILE}"
|
|
81
|
+
local worktree="${3:?usage: adapter_run MODE SESSION WORKTREE PROMPT_FILE}"
|
|
82
|
+
local prompt_file="${4:?usage: adapter_run MODE SESSION WORKTREE PROMPT_FILE}"
|
|
83
|
+
|
|
84
|
+
# Validate prompt file
|
|
85
|
+
if [[ ! -f "${prompt_file}" ]]; then
|
|
86
|
+
echo "ERROR: Prompt file not found: ${prompt_file}"
|
|
87
|
+
return 1
|
|
88
|
+
fi
|
|
89
|
+
if [[ ! -s "${prompt_file}" ]]; then
|
|
90
|
+
echo "ERROR: Prompt file is empty: ${prompt_file}"
|
|
91
|
+
return 1
|
|
92
|
+
fi
|
|
93
|
+
|
|
94
|
+
local timeout_seconds="${OLLAMA_TIMEOUT_SECONDS:-900}"
|
|
95
|
+
|
|
96
|
+
echo "Ollama adapter: Running session ${session} with model ${ADAPTER_MODEL}"
|
|
97
|
+
|
|
98
|
+
# Read the prompt
|
|
99
|
+
local prompt
|
|
100
|
+
prompt="$(cat "${prompt_file}")"
|
|
101
|
+
|
|
102
|
+
# Run ollama with the prompt
|
|
103
|
+
# Use perl for timeout on macOS (which lacks GNU timeout)
|
|
104
|
+
if command -v timeout >/dev/null 2>&1; then
|
|
105
|
+
if ! timeout "${timeout_seconds}" ollama run "${ADAPTER_MODEL}" "${prompt}" 2>&1; then
|
|
106
|
+
echo "ERROR: Ollama run failed or timed out after ${timeout_seconds}s"
|
|
107
|
+
return 1
|
|
108
|
+
fi
|
|
109
|
+
elif command -v perl >/dev/null 2>&1; then
|
|
110
|
+
if ! perl -e "alarm ${timeout_seconds}; exec @ARGV" ollama run "${ADAPTER_MODEL}" "${prompt}" 2>&1; then
|
|
111
|
+
echo "ERROR: Ollama run failed or timed out after ${timeout_seconds}s"
|
|
112
|
+
return 1
|
|
113
|
+
fi
|
|
114
|
+
else
|
|
115
|
+
# No timeout available, run without timeout
|
|
116
|
+
if ! ollama run "${ADAPTER_MODEL}" "${prompt}" 2>&1; then
|
|
117
|
+
echo "ERROR: Ollama run failed"
|
|
118
|
+
return 1
|
|
119
|
+
fi
|
|
120
|
+
fi
|
|
121
|
+
|
|
122
|
+
echo "Ollama adapter: Session ${session} completed"
|
|
123
|
+
return 0
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
# Status check (override default)
|
|
127
|
+
adapter_status() {
|
|
128
|
+
local runs_root="${1:?usage: adapter_status RUNS_ROOT SESSION}"
|
|
129
|
+
local session="${2:?usage: adapter_status RUNS_ROOT SESSION}"
|
|
130
|
+
local run_dir="${runs_root}/${session}"
|
|
131
|
+
|
|
132
|
+
if [[ ! -d "$run_dir" ]]; then
|
|
133
|
+
echo "NOT_FOUND"
|
|
134
|
+
return 1
|
|
135
|
+
fi
|
|
136
|
+
|
|
137
|
+
# Check for result file
|
|
138
|
+
if [[ -f "$run_dir/result.env" ]]; then
|
|
139
|
+
source "$run_dir/result.env"
|
|
140
|
+
echo "${OUTCOME:-UNKNOWN}"
|
|
141
|
+
return 0
|
|
142
|
+
fi
|
|
143
|
+
|
|
144
|
+
# Check if ollama process is running
|
|
145
|
+
if pgrep -f "ollama run ${ADAPTER_MODEL}" >/dev/null 2>&1; then
|
|
146
|
+
echo "RUNNING"
|
|
147
|
+
return 0
|
|
148
|
+
fi
|
|
149
|
+
|
|
150
|
+
echo "UNKNOWN"
|
|
151
|
+
return 0
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
# Self-register: validate this adapter implements required functions
|
|
155
|
+
if [[ "${BASH_SOURCE[0]}" == "${0}" ]]; then
|
|
156
|
+
# Running directly - print info
|
|
157
|
+
adapter_info
|
|
158
|
+
echo "---"
|
|
159
|
+
adapter_health_check
|
|
160
|
+
fi
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# openclaw-adapter.sh
|
|
3
|
+
# Adapter implementation for OpenClaw.
|
|
4
|
+
|
|
5
|
+
set -euo pipefail
|
|
6
|
+
|
|
7
|
+
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
|
8
|
+
source "${SCRIPT_DIR}/adapter-interface.sh"
|
|
9
|
+
source "${SCRIPT_DIR}/adapter-capabilities.sh"
|
|
10
|
+
|
|
11
|
+
ADAPTER_ID="openclaw"
|
|
12
|
+
ADAPTER_NAME="OpenClaw"
|
|
13
|
+
ADAPTER_TYPE="cloud-api"
|
|
14
|
+
ADAPTER_VERSION="1.0.0"
|
|
15
|
+
ADAPTER_MODEL="${OPENCLAW_MODEL:-openrouter/qwen/qwen3.5-plus:free}"
|
|
16
|
+
|
|
17
|
+
# OpenClaw capabilities
|
|
18
|
+
ADAPTER_CAP_CLOUD_API=true
|
|
19
|
+
ADAPTER_CAP_STREAMING=true
|
|
20
|
+
ADAPTER_CAP_JSON_OUTPUT=true
|
|
21
|
+
ADAPTER_CAP_RESIDENT_MODE=true
|
|
22
|
+
ADAPTER_CAP_MAX_TIMEOUT=900
|
|
23
|
+
|
|
24
|
+
adapter_info() {
|
|
25
|
+
cat <<EOF
|
|
26
|
+
id=${ADAPTER_ID}
|
|
27
|
+
name=${ADAPTER_NAME}
|
|
28
|
+
type=${ADAPTER_TYPE}
|
|
29
|
+
version=${ADAPTER_VERSION}
|
|
30
|
+
model=${ADAPTER_MODEL}
|
|
31
|
+
EOF
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
adapter_health_check() {
|
|
35
|
+
if ! command -v openclaw >/dev/null 2>&1; then
|
|
36
|
+
echo "ERROR: openclaw CLI not found in PATH"
|
|
37
|
+
return 1
|
|
38
|
+
fi
|
|
39
|
+
echo "OK: OpenClaw adapter healthy"
|
|
40
|
+
return 0
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
adapter_run() {
|
|
44
|
+
local mode="${1:?usage: adapter_run MODE SESSION WORKTREE PROMPT_FILE}"
|
|
45
|
+
local session="${2:?usage: adapter_run MODE SESSION WORKTREE PROMPT_FILE}"
|
|
46
|
+
local worktree="${3:?usage: adapter_run MODE SESSION WORKTREE PROMPT_FILE}"
|
|
47
|
+
local prompt_file="${4:?usage: adapter_run MODE SESSION WORKTREE PROMPT_FILE}"
|
|
48
|
+
|
|
49
|
+
local timeout_seconds="${OPENCLAW_TIMEOUT_SECONDS:-900}"
|
|
50
|
+
|
|
51
|
+
echo "OpenClaw adapter: Running session ${session}"
|
|
52
|
+
|
|
53
|
+
cd "${worktree}" || return 1
|
|
54
|
+
|
|
55
|
+
prompt="$(cat "${prompt_file}")"
|
|
56
|
+
|
|
57
|
+
if ! timeout "${timeout_seconds}" openclaw --model "${ADAPTER_MODEL}" "${prompt}" 2>&1; then
|
|
58
|
+
echo "ERROR: OpenClaw run failed"
|
|
59
|
+
return 1
|
|
60
|
+
fi
|
|
61
|
+
|
|
62
|
+
return 0
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
if [[ "${BASH_SOURCE[0]}" == "${0}" ]]; then
|
|
66
|
+
adapter_info
|
|
67
|
+
echo "---"
|
|
68
|
+
adapter_health_check
|
|
69
|
+
fi
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# opencode-adapter.sh
|
|
3
|
+
# Adapter implementation for OpenCode (Crush)
|
|
4
|
+
|
|
5
|
+
set -euo pipefail
|
|
6
|
+
|
|
7
|
+
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
|
8
|
+
source "${SCRIPT_DIR}/adapter-interface.sh"
|
|
9
|
+
source "${SCRIPT_DIR}/adapter-capabilities.sh"
|
|
10
|
+
|
|
11
|
+
ADAPTER_ID="opencode"
|
|
12
|
+
ADAPTER_NAME="OpenCode (Crush)"
|
|
13
|
+
ADAPTER_TYPE="cloud-api"
|
|
14
|
+
ADAPTER_VERSION="1.0.0"
|
|
15
|
+
ADAPTER_MODEL="${OPENCODE_MODEL:-anthropic/claude-sonnet-4-20250514}"
|
|
16
|
+
|
|
17
|
+
# OpenCode capabilities
|
|
18
|
+
ADAPTER_CAP_CLOUD_API=true
|
|
19
|
+
ADAPTER_CAP_STREAMING=true
|
|
20
|
+
ADAPTER_CAP_JSON_OUTPUT=true
|
|
21
|
+
ADAPTER_CAP_MAX_TIMEOUT=900
|
|
22
|
+
|
|
23
|
+
adapter_info() {
|
|
24
|
+
cat <<EOF
|
|
25
|
+
id=${ADAPTER_ID}
|
|
26
|
+
name=${ADAPTER_NAME}
|
|
27
|
+
type=${ADAPTER_TYPE}
|
|
28
|
+
version=${ADAPTER_VERSION}
|
|
29
|
+
model=${ADAPTER_MODEL}
|
|
30
|
+
EOF
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
adapter_health_check() {
|
|
34
|
+
if ! command -v crush >/dev/null 2>&1; then
|
|
35
|
+
echo "ERROR: crush CLI not found in PATH"
|
|
36
|
+
return 1
|
|
37
|
+
fi
|
|
38
|
+
|
|
39
|
+
# Verify crush can actually run (version check)
|
|
40
|
+
if ! crush --version >/dev/null 2>&1; then
|
|
41
|
+
echo "ERROR: crush CLI cannot run (check installation)"
|
|
42
|
+
return 1
|
|
43
|
+
fi
|
|
44
|
+
|
|
45
|
+
local version
|
|
46
|
+
version="$(crush --version 2>/dev/null || true)"
|
|
47
|
+
if [[ -z "$version" ]]; then
|
|
48
|
+
echo "WARN: Could not detect crush version"
|
|
49
|
+
else
|
|
50
|
+
echo "INFO: Crush version: $version"
|
|
51
|
+
fi
|
|
52
|
+
|
|
53
|
+
# Verify model is specified
|
|
54
|
+
if [[ -z "${ADAPTER_MODEL}" ]]; then
|
|
55
|
+
echo "WARN: No model specified for OpenCode adapter"
|
|
56
|
+
fi
|
|
57
|
+
|
|
58
|
+
echo "OK: OpenCode adapter healthy (model: ${ADAPTER_MODEL})"
|
|
59
|
+
return 0
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
adapter_run() {
|
|
63
|
+
local mode="${1:?usage: adapter_run MODE SESSION WORKTREE PROMPT_FILE}"
|
|
64
|
+
local session="${2:?usage: adapter_run MODE SESSION WORKTREE PROMPT_FILE}"
|
|
65
|
+
local worktree="${3:?usage: adapter_run MODE SESSION WORKTREE PROMPT_FILE}"
|
|
66
|
+
local prompt_file="${4:?usage: adapter_run MODE SESSION WORKTREE PROMPT_FILE}"
|
|
67
|
+
|
|
68
|
+
# Validate prompt file
|
|
69
|
+
if [[ ! -f "${prompt_file}" ]]; then
|
|
70
|
+
echo "ERROR: Prompt file not found: ${prompt_file}"
|
|
71
|
+
return 1
|
|
72
|
+
fi
|
|
73
|
+
if [[ ! -s "${prompt_file}" ]]; then
|
|
74
|
+
echo "ERROR: Prompt file is empty: ${prompt_file}"
|
|
75
|
+
return 1
|
|
76
|
+
fi
|
|
77
|
+
|
|
78
|
+
local timeout_seconds="${OPENCODE_TIMEOUT_SECONDS:-900}"
|
|
79
|
+
|
|
80
|
+
echo "OpenCode adapter: Running session ${session} with model ${ADAPTER_MODEL}"
|
|
81
|
+
|
|
82
|
+
cd "${worktree}" || return 1
|
|
83
|
+
|
|
84
|
+
prompt="$(cat "${prompt_file}")"
|
|
85
|
+
|
|
86
|
+
if ! timeout "${timeout_seconds}" crush --model "${ADAPTER_MODEL}" "${prompt}" 2>&1; then
|
|
87
|
+
echo "ERROR: OpenCode run failed"
|
|
88
|
+
return 1
|
|
89
|
+
fi
|
|
90
|
+
|
|
91
|
+
return 0
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
if [[ "${BASH_SOURCE[0]}" == "${0}" ]]; then
|
|
95
|
+
adapter_info
|
|
96
|
+
echo "---"
|
|
97
|
+
adapter_health_check
|
|
98
|
+
fi
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# pi-adapter.sh
|
|
3
|
+
# Adapter implementation for Pi CLI (OpenRouter)
|
|
4
|
+
|
|
5
|
+
set -euo pipefail
|
|
6
|
+
|
|
7
|
+
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
|
8
|
+
source "${SCRIPT_DIR}/adapter-interface.sh"
|
|
9
|
+
source "${SCRIPT_DIR}/adapter-capabilities.sh"
|
|
10
|
+
|
|
11
|
+
ADAPTER_ID="pi"
|
|
12
|
+
ADAPTER_NAME="Pi Coding Agent"
|
|
13
|
+
ADAPTER_TYPE="cloud-api"
|
|
14
|
+
ADAPTER_VERSION="1.0.0"
|
|
15
|
+
ADAPTER_MODEL="${PI_MODEL:-openrouter/qwen/qwen3.5-plus:free}"
|
|
16
|
+
|
|
17
|
+
# Pi capabilities
|
|
18
|
+
ADAPTER_CAP_CLOUD_API=true
|
|
19
|
+
ADAPTER_CAP_STREAMING=true
|
|
20
|
+
ADAPTER_CAP_JSON_OUTPUT=true
|
|
21
|
+
ADAPTER_CAP_MAX_TIMEOUT=900
|
|
22
|
+
|
|
23
|
+
adapter_info() {
|
|
24
|
+
cat <<EOF
|
|
25
|
+
id=${ADAPTER_ID}
|
|
26
|
+
name=${ADAPTER_NAME}
|
|
27
|
+
type=${ADAPTER_TYPE}
|
|
28
|
+
version=${ADAPTER_VERSION}
|
|
29
|
+
model=${ADAPTER_MODEL}
|
|
30
|
+
EOF
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
adapter_health_check() {
|
|
34
|
+
if ! command -v pi >/dev/null 2>&1; then
|
|
35
|
+
echo "ERROR: pi CLI not found in PATH"
|
|
36
|
+
return 1
|
|
37
|
+
fi
|
|
38
|
+
|
|
39
|
+
if [[ -z "${OPENROUTER_API_KEY:-}" ]]; then
|
|
40
|
+
echo "ERROR: OPENROUTER_API_KEY is required for Pi adapter"
|
|
41
|
+
return 1
|
|
42
|
+
fi
|
|
43
|
+
|
|
44
|
+
# Test API connectivity with a simple model list call
|
|
45
|
+
if ! pi models 2>/dev/null | grep -q "."; then
|
|
46
|
+
echo "ERROR: Pi CLI cannot connect to OpenRouter API (check OPENROUTER_API_KEY)"
|
|
47
|
+
return 1
|
|
48
|
+
fi
|
|
49
|
+
|
|
50
|
+
# Verify configured model is available
|
|
51
|
+
if [[ -n "${ADAPTER_MODEL}" ]] && ! pi models 2>/dev/null | grep -q "${ADAPTER_MODEL}"; then
|
|
52
|
+
echo "WARN: Configured model ${ADAPTER_MODEL} not found in available models"
|
|
53
|
+
fi
|
|
54
|
+
|
|
55
|
+
echo "OK: Pi adapter healthy (model: ${ADAPTER_MODEL})"
|
|
56
|
+
return 0
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
adapter_run() {
|
|
60
|
+
local mode="${1:?usage: adapter_run MODE SESSION WORKTREE PROMPT_FILE}"
|
|
61
|
+
local session="${2:?usage: adapter_run MODE SESSION WORKTREE PROMPT_FILE}"
|
|
62
|
+
local worktree="${3:?usage: adapter_run MODE SESSION WORKTREE PROMPT_FILE}"
|
|
63
|
+
local prompt_file="${4:?usage: adapter_run MODE SESSION WORKTREE PROMPT_FILE}"
|
|
64
|
+
|
|
65
|
+
# Validate prompt file
|
|
66
|
+
if [[ ! -f "${prompt_file}" ]]; then
|
|
67
|
+
echo "ERROR: Prompt file not found: ${prompt_file}"
|
|
68
|
+
return 1
|
|
69
|
+
fi
|
|
70
|
+
if [[ ! -s "${prompt_file}" ]]; then
|
|
71
|
+
echo "ERROR: Prompt file is empty: ${prompt_file}"
|
|
72
|
+
return 1
|
|
73
|
+
fi
|
|
74
|
+
|
|
75
|
+
local timeout_seconds="${PI_TIMEOUT_SECONDS:-900}"
|
|
76
|
+
|
|
77
|
+
echo "Pi adapter: Running session ${session}"
|
|
78
|
+
|
|
79
|
+
cd "${worktree}" || return 1
|
|
80
|
+
|
|
81
|
+
prompt="$(cat "${prompt_file}")"
|
|
82
|
+
|
|
83
|
+
if ! timeout "${timeout_seconds}" pi --model "${ADAPTER_MODEL}" "${prompt}" 2>&1; then
|
|
84
|
+
echo "ERROR: Pi run failed"
|
|
85
|
+
return 1
|
|
86
|
+
fi
|
|
87
|
+
|
|
88
|
+
return 0
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
if [[ "${BASH_SOURCE[0]}" == "${0}" ]]; then
|
|
92
|
+
adapter_info
|
|
93
|
+
echo "---"
|
|
94
|
+
adapter_health_check
|
|
95
|
+
fi
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# run-with-adapter.sh
|
|
3
|
+
# Generic runner that uses the adapter interface
|
|
4
|
+
# Usage: run-with-adapter.sh ADAPTER_SCRIPT MODE SESSION WORKTREE PROMPT_FILE
|
|
5
|
+
|
|
6
|
+
set -euo pipefail
|
|
7
|
+
|
|
8
|
+
ADAPTER_SCRIPT="${1:?usage: run-with-adapter.sh ADAPTER_SCRIPT MODE SESSION WORKTREE PROMPT_FILE}"
|
|
9
|
+
MODE="${2:?usage: run-with-adapter.sh ADAPTER_SCRIPT MODE SESSION WORKTREE PROMPT_FILE}"
|
|
10
|
+
SESSION="${3:?usage: run-with-adapter.sh ADAPTER_SCRIPT MODE SESSION WORKTREE PROMPT_FILE}"
|
|
11
|
+
WORKTREE="${4:?usage: run-with-adapter.sh ADAPTER_SCRIPT MODE SESSION WORKTREE PROMPT_FILE}"
|
|
12
|
+
PROMPT_FILE="${5:?usage: run-with-adapter.sh ADAPTER_SCRIPT MODE SESSION WORKTREE PROMPT_FILE}"
|
|
13
|
+
|
|
14
|
+
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
|
15
|
+
|
|
16
|
+
# Load adapter interface
|
|
17
|
+
source "${SCRIPT_DIR}/adapter-interface.sh"
|
|
18
|
+
|
|
19
|
+
# Load adapter implementation
|
|
20
|
+
adapter_load "${ADAPTER_SCRIPT}"
|
|
21
|
+
|
|
22
|
+
# Validate adapter
|
|
23
|
+
if ! adapter_validate; then
|
|
24
|
+
echo "ERROR: Adapter validation failed"
|
|
25
|
+
exit 1
|
|
26
|
+
fi
|
|
27
|
+
|
|
28
|
+
# Print adapter info
|
|
29
|
+
echo "=== Using Adapter ==="
|
|
30
|
+
adapter_info
|
|
31
|
+
echo "===================="
|
|
32
|
+
|
|
33
|
+
# Run the task
|
|
34
|
+
adapter_run "${MODE}" "${SESSION}" "${WORKTREE}" "${PROMPT_FILE}"
|