claude-evolve 1.5.1 → 1.5.2
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/bin/claude-evolve-ideate +389 -276
- package/bin/claude-evolve-run +15 -3
- package/bin/claude-evolve-worker +19 -215
- package/lib/ai-cli.sh +221 -0
- package/lib/config.sh +71 -1
- package/package.json +1 -1
- package/templates/config.yaml +17 -1
package/bin/claude-evolve-run
CHANGED
|
@@ -518,10 +518,22 @@ while true; do
|
|
|
518
518
|
|
|
519
519
|
# Generate new ideas using the multi-strategy approach
|
|
520
520
|
echo "[DISPATCHER] Calling claude-evolve-ideate to generate new candidates..."
|
|
521
|
+
# Export config path for ideate script
|
|
522
|
+
if [[ -n $WORKER_CONFIG_PATH ]]; then
|
|
523
|
+
export CLAUDE_EVOLVE_CONFIG="$WORKER_CONFIG_PATH"
|
|
524
|
+
fi
|
|
521
525
|
if ! "$ideate_script"; then
|
|
522
|
-
echo "[
|
|
523
|
-
|
|
524
|
-
|
|
526
|
+
echo "[WARN] Ideation failed to generate new ideas" >&2
|
|
527
|
+
if [[ "${CONTINUE_ON_IDEATION_FAILURE:-false}" == "true" ]]; then
|
|
528
|
+
echo "[DISPATCHER] Continuing despite ideation failure (CONTINUE_ON_IDEATION_FAILURE=true)" >&2
|
|
529
|
+
echo "[DISPATCHER] Waiting 60 seconds before retry..." >&2
|
|
530
|
+
sleep 60
|
|
531
|
+
continue
|
|
532
|
+
else
|
|
533
|
+
echo "[DISPATCHER] Evolution complete - ideation failed."
|
|
534
|
+
echo "[DISPATCHER] Set CONTINUE_ON_IDEATION_FAILURE=true to continue despite failures"
|
|
535
|
+
break
|
|
536
|
+
fi
|
|
525
537
|
fi
|
|
526
538
|
|
|
527
539
|
echo "[DISPATCHER] New ideas generated successfully. Continuing evolution..."
|
package/bin/claude-evolve-worker
CHANGED
|
@@ -5,6 +5,7 @@ set -e
|
|
|
5
5
|
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" &> /dev/null && pwd)"
|
|
6
6
|
source "$SCRIPT_DIR/../lib/config.sh"
|
|
7
7
|
source "$SCRIPT_DIR/../lib/csv-lock.sh"
|
|
8
|
+
source "$SCRIPT_DIR/../lib/ai-cli.sh"
|
|
8
9
|
|
|
9
10
|
# Track current candidate for cleanup
|
|
10
11
|
CURRENT_CANDIDATE_ID=""
|
|
@@ -79,148 +80,6 @@ else
|
|
|
79
80
|
load_config
|
|
80
81
|
fi
|
|
81
82
|
|
|
82
|
-
# Call an AI model with a prompt - handles model-specific invocation
|
|
83
|
-
call_ai_model() {
|
|
84
|
-
local model="$1"
|
|
85
|
-
local prompt="$2"
|
|
86
|
-
local ai_output
|
|
87
|
-
local ai_exit_code
|
|
88
|
-
|
|
89
|
-
case "$model" in
|
|
90
|
-
"claude")
|
|
91
|
-
# Pass prompt as argument, not via stdin
|
|
92
|
-
ai_output=$(timeout 300 claude --dangerously-skip-permissions -p "$prompt" 2>&1)
|
|
93
|
-
ai_exit_code=$?
|
|
94
|
-
;;
|
|
95
|
-
|
|
96
|
-
"gemini")
|
|
97
|
-
# Pass prompt as argument, not via stdin
|
|
98
|
-
ai_output=$(timeout 300 gemini -y -p "$prompt" 2>&1)
|
|
99
|
-
ai_exit_code=$?
|
|
100
|
-
;;
|
|
101
|
-
|
|
102
|
-
"codex")
|
|
103
|
-
# Pass prompt as argument, not via stdin
|
|
104
|
-
ai_output=$(timeout 300 codex exec --dangerously-bypass-approvals-and-sandbox "$prompt" 2>&1)
|
|
105
|
-
ai_exit_code=$?
|
|
106
|
-
;;
|
|
107
|
-
|
|
108
|
-
*)
|
|
109
|
-
echo "[WORKER-$$] ERROR: Unknown AI model: $model" >&2
|
|
110
|
-
return 1
|
|
111
|
-
;;
|
|
112
|
-
esac
|
|
113
|
-
|
|
114
|
-
# Return output via stdout
|
|
115
|
-
echo "$ai_output"
|
|
116
|
-
return $ai_exit_code
|
|
117
|
-
}
|
|
118
|
-
|
|
119
|
-
# Check if AI output indicates a usage limit was hit
|
|
120
|
-
is_usage_limit_error() {
|
|
121
|
-
local output="$1"
|
|
122
|
-
local model="$2"
|
|
123
|
-
|
|
124
|
-
case "$model" in
|
|
125
|
-
"claude")
|
|
126
|
-
echo "$output" | grep -q "Claude AI usage limit reached"
|
|
127
|
-
;;
|
|
128
|
-
"gemini")
|
|
129
|
-
echo "$output" | grep -q "Quota exceeded.*Gemini"
|
|
130
|
-
;;
|
|
131
|
-
"codex")
|
|
132
|
-
# Add codex-specific limit patterns if they exist
|
|
133
|
-
false
|
|
134
|
-
;;
|
|
135
|
-
*)
|
|
136
|
-
false
|
|
137
|
-
;;
|
|
138
|
-
esac
|
|
139
|
-
}
|
|
140
|
-
|
|
141
|
-
# Validate if AI output is successful
|
|
142
|
-
is_valid_ai_output() {
|
|
143
|
-
local output="$1"
|
|
144
|
-
local model="$2"
|
|
145
|
-
local exit_code="$3"
|
|
146
|
-
|
|
147
|
-
# First check exit code
|
|
148
|
-
[[ $exit_code -ne 0 ]] && return 1
|
|
149
|
-
|
|
150
|
-
# Model-specific validation
|
|
151
|
-
case "$model" in
|
|
152
|
-
"claude")
|
|
153
|
-
# Claude is straightforward - exit code 0 means success
|
|
154
|
-
return 0
|
|
155
|
-
;;
|
|
156
|
-
|
|
157
|
-
"gemini")
|
|
158
|
-
# Gemini needs extra validation for auth messages
|
|
159
|
-
if echo "$output" | grep -q "Attempting to authenticate\|Authenticating\|Loading\|Initializing"; then
|
|
160
|
-
return 1
|
|
161
|
-
fi
|
|
162
|
-
# Also check for minimal output
|
|
163
|
-
if [[ -z "$output" ]] || [[ $(echo "$output" | wc -l) -lt 2 ]]; then
|
|
164
|
-
return 1
|
|
165
|
-
fi
|
|
166
|
-
return 0
|
|
167
|
-
;;
|
|
168
|
-
|
|
169
|
-
"codex")
|
|
170
|
-
# Codex might return JSON that needs extraction
|
|
171
|
-
if echo "$output" | grep -q '"content"'; then
|
|
172
|
-
# Will be cleaned later, just check it's not an error
|
|
173
|
-
if echo "$output" | grep -q "error\|failed\|exception"; then
|
|
174
|
-
return 1
|
|
175
|
-
fi
|
|
176
|
-
fi
|
|
177
|
-
[[ -n "$output" ]]
|
|
178
|
-
;;
|
|
179
|
-
|
|
180
|
-
*)
|
|
181
|
-
return 1
|
|
182
|
-
;;
|
|
183
|
-
esac
|
|
184
|
-
}
|
|
185
|
-
|
|
186
|
-
# Clean AI output if needed (e.g., extract from JSON)
|
|
187
|
-
clean_ai_output() {
|
|
188
|
-
local output="$1"
|
|
189
|
-
local model="$2"
|
|
190
|
-
|
|
191
|
-
case "$model" in
|
|
192
|
-
"codex")
|
|
193
|
-
# Clean codex output - extract content between "codex" marker and "tokens used"
|
|
194
|
-
if echo "$output" | grep -q "^\[.*\] codex$"; then
|
|
195
|
-
# Extract content between "codex" line and "tokens used" line
|
|
196
|
-
output=$(echo "$output" | awk '/\] codex$/{flag=1;next}/\] tokens used/{flag=0}flag')
|
|
197
|
-
elif echo "$output" | grep -q '"content"'; then
|
|
198
|
-
# Old JSON format
|
|
199
|
-
output=$(echo "$output" | python3 -c "
|
|
200
|
-
import sys
|
|
201
|
-
import json
|
|
202
|
-
try:
|
|
203
|
-
data = json.load(sys.stdin)
|
|
204
|
-
if 'content' in data:
|
|
205
|
-
print(data['content'])
|
|
206
|
-
elif 'response' in data:
|
|
207
|
-
print(data['response'])
|
|
208
|
-
elif 'text' in data:
|
|
209
|
-
print(data['text'])
|
|
210
|
-
else:
|
|
211
|
-
print(json.dumps(data))
|
|
212
|
-
except:
|
|
213
|
-
print(sys.stdin.read())
|
|
214
|
-
" 2>/dev/null || echo "$output")
|
|
215
|
-
fi
|
|
216
|
-
# Trim whitespace
|
|
217
|
-
output=$(echo "$output" | sed -e 's/^[[:space:]]*//' -e 's/[[:space:]]*$//')
|
|
218
|
-
;;
|
|
219
|
-
esac
|
|
220
|
-
|
|
221
|
-
echo "$output"
|
|
222
|
-
}
|
|
223
|
-
|
|
224
83
|
# AI round-robin with fallback function for code evolution
|
|
225
84
|
call_ai_for_evolution() {
|
|
226
85
|
local prompt="$1"
|
|
@@ -237,83 +96,26 @@ call_ai_for_evolution() {
|
|
|
237
96
|
# Calculate hash for round-robin (combine generation and ID)
|
|
238
97
|
local hash_value=$((gen_num * 1000 + id_num))
|
|
239
98
|
|
|
240
|
-
#
|
|
241
|
-
local
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
fi
|
|
245
|
-
if command -v gemini >/dev/null 2>&1; then
|
|
246
|
-
available_models+=("gemini")
|
|
247
|
-
fi
|
|
248
|
-
if command -v codex >/dev/null 2>&1; then
|
|
249
|
-
available_models+=("codex")
|
|
250
|
-
fi
|
|
251
|
-
|
|
252
|
-
if [[ ${#available_models[@]} -eq 0 ]]; then
|
|
253
|
-
echo "[WORKER-$$] ERROR: No AI models available!" >&2
|
|
254
|
-
return 1
|
|
255
|
-
fi
|
|
256
|
-
|
|
257
|
-
# Create ordered list based on round-robin for this candidate
|
|
258
|
-
local num_models=${#available_models[@]}
|
|
259
|
-
local start_index=$((hash_value % num_models))
|
|
260
|
-
local models=()
|
|
261
|
-
|
|
262
|
-
# Add models in round-robin order starting from the calculated index
|
|
263
|
-
for ((i=0; i<num_models; i++)); do
|
|
264
|
-
local idx=$(((start_index + i) % num_models))
|
|
265
|
-
models+=("${available_models[$idx]}")
|
|
266
|
-
done
|
|
267
|
-
|
|
268
|
-
echo "[WORKER-$$] Model order for $candidate_id (round-robin): ${models[*]}" >&2
|
|
269
|
-
|
|
270
|
-
# Track if any model hit usage limits
|
|
271
|
-
local hit_usage_limit=false
|
|
272
|
-
local limited_models=()
|
|
273
|
-
|
|
274
|
-
# Try each model in the ordered sequence
|
|
275
|
-
for model in "${models[@]}"; do
|
|
276
|
-
echo "[WORKER-$$] Attempting code evolution with $model" >&2
|
|
277
|
-
|
|
278
|
-
# Call the AI model
|
|
279
|
-
local ai_output
|
|
280
|
-
ai_output=$(call_ai_model "$model" "$prompt")
|
|
281
|
-
local ai_exit_code=$?
|
|
282
|
-
|
|
283
|
-
# Check for usage limits
|
|
284
|
-
if is_usage_limit_error "$ai_output" "$model"; then
|
|
285
|
-
echo "[WORKER-$$] $model hit usage limit - trying next model" >&2
|
|
286
|
-
hit_usage_limit=true
|
|
287
|
-
limited_models+=("$model")
|
|
288
|
-
continue
|
|
289
|
-
fi
|
|
290
|
-
|
|
291
|
-
# Validate output
|
|
292
|
-
if is_valid_ai_output "$ai_output" "$model" "$ai_exit_code"; then
|
|
293
|
-
# Clean output if needed
|
|
294
|
-
ai_output=$(clean_ai_output "$ai_output" "$model")
|
|
295
|
-
echo "[WORKER-$$] $model succeeded" >&2
|
|
296
|
-
# Output the cleaned result for the worker to use
|
|
297
|
-
echo "$ai_output"
|
|
298
|
-
return 0
|
|
299
|
-
fi
|
|
300
|
-
|
|
301
|
-
echo "[WORKER-$$] $model failed (exit code $ai_exit_code), trying next model..." >&2
|
|
302
|
-
if [[ -n "$ai_output" ]]; then
|
|
303
|
-
echo "[WORKER-$$] $model error: $(echo "$ai_output" | head -5)" >&2
|
|
304
|
-
fi
|
|
305
|
-
done
|
|
306
|
-
|
|
307
|
-
# All models have been tried
|
|
308
|
-
echo "[WORKER-$$] All AI models failed for code evolution" >&2
|
|
99
|
+
# Use the centralized AI library for evolution (run command)
|
|
100
|
+
local ai_output
|
|
101
|
+
ai_output=$(call_ai_with_round_robin "$prompt" "run" "$hash_value")
|
|
102
|
+
local ai_exit_code=$?
|
|
309
103
|
|
|
310
|
-
#
|
|
311
|
-
if [[
|
|
312
|
-
echo "[WORKER-$$]
|
|
104
|
+
# Handle special exit codes
|
|
105
|
+
if [[ $ai_exit_code -eq 3 ]]; then
|
|
106
|
+
echo "[WORKER-$$] All models hit usage limits" >&2
|
|
313
107
|
echo "[WORKER-$$] Unable to complete evolution due to API limits" >&2
|
|
314
108
|
exit 3
|
|
315
109
|
fi
|
|
316
110
|
|
|
111
|
+
if [[ $ai_exit_code -eq 0 ]]; then
|
|
112
|
+
echo "[WORKER-$$] AI succeeded" >&2
|
|
113
|
+
# Output the result for the worker to use
|
|
114
|
+
echo "$ai_output"
|
|
115
|
+
return 0
|
|
116
|
+
fi
|
|
117
|
+
|
|
118
|
+
echo "[WORKER-$$] All AI models failed" >&2
|
|
317
119
|
return 1
|
|
318
120
|
}
|
|
319
121
|
|
|
@@ -400,7 +202,9 @@ with EvolutionCSV('$FULL_CSV_PATH') as csv:
|
|
|
400
202
|
|
|
401
203
|
The modification should be substantial and follow the description exactly. Make sure the algorithm still follows all interface requirements and can run properly.
|
|
402
204
|
|
|
403
|
-
Important: Make meaningful changes that match the description. Don't just add comments or make trivial adjustments.
|
|
205
|
+
Important: Make meaningful changes that match the description. Don't just add comments or make trivial adjustments.
|
|
206
|
+
|
|
207
|
+
CRITICAL: Do NOT use any git commands (git add, git commit, git reset, etc.). Only modify the file directly."
|
|
404
208
|
|
|
405
209
|
if [[ "$is_baseline" != "true" ]]; then
|
|
406
210
|
# Change to evolution directory so AI can access files
|
package/lib/ai-cli.sh
ADDED
|
@@ -0,0 +1,221 @@
|
|
|
1
|
+
#!/bin/bash
|
|
2
|
+
# Centralized AI CLI invocation library for claude-evolve
|
|
3
|
+
|
|
4
|
+
# Source config to get LLM_CLI array and model lists
|
|
5
|
+
# This will be sourced after config.sh in the main scripts
|
|
6
|
+
|
|
7
|
+
# Call an AI model using the configured command template
|
|
8
|
+
# Usage: call_ai_model_configured <model_name> <prompt>
|
|
9
|
+
# Returns: 0 on success, non-zero on failure
|
|
10
|
+
# Output: AI response on stdout
|
|
11
|
+
call_ai_model_configured() {
|
|
12
|
+
local model_name="$1"
|
|
13
|
+
local prompt="$2"
|
|
14
|
+
|
|
15
|
+
# Build command directly based on model
|
|
16
|
+
case "$model_name" in
|
|
17
|
+
opus|sonnet)
|
|
18
|
+
local ai_output
|
|
19
|
+
ai_output=$(timeout 300 claude --dangerously-skip-permissions --model "$model_name" -p "$prompt" 2>&1)
|
|
20
|
+
local ai_exit_code=$?
|
|
21
|
+
;;
|
|
22
|
+
o3)
|
|
23
|
+
local ai_output
|
|
24
|
+
ai_output=$(timeout 300 codex exec -m o3 --dangerously-bypass-approvals-and-sandbox "$prompt" 2>&1)
|
|
25
|
+
local ai_exit_code=$?
|
|
26
|
+
;;
|
|
27
|
+
codex)
|
|
28
|
+
local ai_output
|
|
29
|
+
ai_output=$(timeout 300 codex exec --dangerously-bypass-approvals-and-sandbox "$prompt" 2>&1)
|
|
30
|
+
local ai_exit_code=$?
|
|
31
|
+
;;
|
|
32
|
+
gemini)
|
|
33
|
+
# Debug: Show exact command
|
|
34
|
+
echo "[DEBUG] Running: timeout 300 gemini -y -p <prompt>" >&2
|
|
35
|
+
echo "[DEBUG] Working directory: $(pwd)" >&2
|
|
36
|
+
echo "[DEBUG] Files in current dir:" >&2
|
|
37
|
+
ls -la temp-csv-*.csv 2>&1 | head -5 >&2
|
|
38
|
+
local ai_output
|
|
39
|
+
ai_output=$(timeout 300 gemini -y -p "$prompt" 2>&1)
|
|
40
|
+
local ai_exit_code=$?
|
|
41
|
+
;;
|
|
42
|
+
*)
|
|
43
|
+
echo "[ERROR] Unknown model: $model_name" >&2
|
|
44
|
+
return 1
|
|
45
|
+
;;
|
|
46
|
+
esac
|
|
47
|
+
|
|
48
|
+
# Debug: log model and prompt size
|
|
49
|
+
echo "[DEBUG] Calling $model_name with prompt of ${#prompt} characters" >&2
|
|
50
|
+
|
|
51
|
+
# Always log basic info
|
|
52
|
+
echo "[AI] $model_name exit code: $ai_exit_code, output length: ${#ai_output} chars" >&2
|
|
53
|
+
|
|
54
|
+
# Show detailed output if verbose or if there was an error
|
|
55
|
+
if [[ "${VERBOSE_AI_OUTPUT:-false}" == "true" ]] || [[ $ai_exit_code -ne 0 ]]; then
|
|
56
|
+
echo "[AI] Raw output from $model_name:" >&2
|
|
57
|
+
echo "----------------------------------------" >&2
|
|
58
|
+
if [[ ${#ai_output} -gt 2000 ]]; then
|
|
59
|
+
echo "$ai_output" | head -50 >&2
|
|
60
|
+
echo "... (truncated from ${#ai_output} characters to first 50 lines) ..." >&2
|
|
61
|
+
else
|
|
62
|
+
echo "$ai_output" >&2
|
|
63
|
+
fi
|
|
64
|
+
echo "----------------------------------------" >&2
|
|
65
|
+
fi
|
|
66
|
+
|
|
67
|
+
# Debug: save full output if debugging is enabled
|
|
68
|
+
if [[ "${DEBUG_AI_CALLS:-}" == "true" ]]; then
|
|
69
|
+
local debug_file="/tmp/claude-evolve-ai-${model_name}-$$.log"
|
|
70
|
+
echo "Model: $model_name" > "$debug_file"
|
|
71
|
+
echo "Exit code: $ai_exit_code" >> "$debug_file"
|
|
72
|
+
echo "Prompt length: ${#prompt}" >> "$debug_file"
|
|
73
|
+
echo "Output:" >> "$debug_file"
|
|
74
|
+
echo "$ai_output" >> "$debug_file"
|
|
75
|
+
echo "[DEBUG] Full output saved to: $debug_file" >&2
|
|
76
|
+
fi
|
|
77
|
+
|
|
78
|
+
# Output the result
|
|
79
|
+
echo "$ai_output"
|
|
80
|
+
return $ai_exit_code
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
# DEPRECATED - Keep for compatibility but always return false
|
|
84
|
+
is_usage_limit_error() {
|
|
85
|
+
return 1
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
# DEPRECATED - Just check exit code now
|
|
89
|
+
is_valid_ai_output() {
|
|
90
|
+
local output="$1"
|
|
91
|
+
local exit_code="$2"
|
|
92
|
+
|
|
93
|
+
# Only check exit code - let the caller verify file changes
|
|
94
|
+
return $exit_code
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
# Clean AI output if needed (e.g., extract from JSON)
|
|
98
|
+
clean_ai_output() {
|
|
99
|
+
local output="$1"
|
|
100
|
+
local model_name="$2"
|
|
101
|
+
|
|
102
|
+
# Handle codex-specific output format
|
|
103
|
+
if [[ "$model_name" == "codex" || "$model_name" == "o3" ]]; then
|
|
104
|
+
# Clean codex output - extract content between "codex" marker and "tokens used"
|
|
105
|
+
if echo "$output" | grep -q "^\[.*\] codex$"; then
|
|
106
|
+
# Extract content between "codex" line and "tokens used" line
|
|
107
|
+
output=$(echo "$output" | awk '/\] codex$/{flag=1;next}/\] tokens used/{flag=0}flag')
|
|
108
|
+
fi
|
|
109
|
+
fi
|
|
110
|
+
|
|
111
|
+
# Trim whitespace
|
|
112
|
+
output=$(echo "$output" | sed -e 's/^[[:space:]]*//' -e 's/[[:space:]]*$//')
|
|
113
|
+
|
|
114
|
+
echo "$output"
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
# Get models for a specific command (run or ideate)
|
|
118
|
+
# Usage: get_models_for_command <command>
|
|
119
|
+
# Returns: Array of model names
|
|
120
|
+
get_models_for_command() {
|
|
121
|
+
local command="$1"
|
|
122
|
+
local model_list=""
|
|
123
|
+
|
|
124
|
+
case "$command" in
|
|
125
|
+
run)
|
|
126
|
+
model_list="$LLM_RUN"
|
|
127
|
+
;;
|
|
128
|
+
ideate)
|
|
129
|
+
model_list="$LLM_IDEATE"
|
|
130
|
+
;;
|
|
131
|
+
*)
|
|
132
|
+
echo "[ERROR] Unknown command: $command" >&2
|
|
133
|
+
return 1
|
|
134
|
+
;;
|
|
135
|
+
esac
|
|
136
|
+
|
|
137
|
+
# Convert space-separated list to array
|
|
138
|
+
echo "$model_list"
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
# Call AI with round-robin and fallback support
|
|
142
|
+
# Usage: call_ai_with_round_robin <prompt> <command> <hash_value>
|
|
143
|
+
# command: "run" or "ideate"
|
|
144
|
+
# hash_value: numeric value for round-robin selection (e.g., candidate ID hash)
|
|
145
|
+
call_ai_with_round_robin() {
|
|
146
|
+
local prompt="$1"
|
|
147
|
+
local command="$2"
|
|
148
|
+
local hash_value="${3:-0}"
|
|
149
|
+
|
|
150
|
+
# Get model list for this command
|
|
151
|
+
local model_list
|
|
152
|
+
model_list=$(get_models_for_command "$command")
|
|
153
|
+
if [[ -z "$model_list" ]]; then
|
|
154
|
+
echo "[ERROR] No models configured for command: $command" >&2
|
|
155
|
+
return 1
|
|
156
|
+
fi
|
|
157
|
+
|
|
158
|
+
# Convert to array
|
|
159
|
+
local models=()
|
|
160
|
+
read -ra models <<< "$model_list"
|
|
161
|
+
|
|
162
|
+
if [[ ${#models[@]} -eq 0 ]]; then
|
|
163
|
+
echo "[ERROR] No models available for $command" >&2
|
|
164
|
+
return 1
|
|
165
|
+
fi
|
|
166
|
+
|
|
167
|
+
# Calculate starting index for round-robin
|
|
168
|
+
local num_models=${#models[@]}
|
|
169
|
+
local start_index=$((hash_value % num_models))
|
|
170
|
+
|
|
171
|
+
# Create ordered list based on round-robin
|
|
172
|
+
local ordered_models=()
|
|
173
|
+
for ((i=0; i<num_models; i++)); do
|
|
174
|
+
local idx=$(((start_index + i) % num_models))
|
|
175
|
+
ordered_models+=("${models[$idx]}")
|
|
176
|
+
done
|
|
177
|
+
|
|
178
|
+
echo "[AI] Model order for $command (round-robin): ${ordered_models[*]}" >&2
|
|
179
|
+
|
|
180
|
+
# Track models that hit usage limits
|
|
181
|
+
local limited_models=()
|
|
182
|
+
local tried_models=()
|
|
183
|
+
|
|
184
|
+
# Try each model in order
|
|
185
|
+
for model in "${ordered_models[@]}"; do
|
|
186
|
+
echo "[AI] Attempting $command with $model" >&2
|
|
187
|
+
tried_models+=("$model")
|
|
188
|
+
|
|
189
|
+
# Call the AI model
|
|
190
|
+
local ai_output
|
|
191
|
+
ai_output=$(call_ai_model_configured "$model" "$prompt")
|
|
192
|
+
local ai_exit_code=$?
|
|
193
|
+
|
|
194
|
+
# Just check exit code - no interpretation
|
|
195
|
+
if [[ $ai_exit_code -eq 0 ]]; then
|
|
196
|
+
# Clean output if needed
|
|
197
|
+
ai_output=$(clean_ai_output "$ai_output" "$model")
|
|
198
|
+
echo "[AI] $model returned exit code 0" >&2
|
|
199
|
+
# Debug: log what AI returned on success
|
|
200
|
+
if [[ "${DEBUG_AI_SUCCESS:-}" == "true" ]]; then
|
|
201
|
+
echo "[AI] $model success output preview:" >&2
|
|
202
|
+
echo "$ai_output" | head -10 >&2
|
|
203
|
+
echo "[AI] (truncated to first 10 lines)" >&2
|
|
204
|
+
fi
|
|
205
|
+
# Output the cleaned result
|
|
206
|
+
echo "$ai_output"
|
|
207
|
+
return 0
|
|
208
|
+
fi
|
|
209
|
+
|
|
210
|
+
echo "[AI] $model returned exit code $ai_exit_code, trying next model..." >&2
|
|
211
|
+
done
|
|
212
|
+
|
|
213
|
+
# All models have been tried
|
|
214
|
+
echo "[AI] All models have been tried without success" >&2
|
|
215
|
+
return 1
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
# Legacy function name for compatibility
|
|
219
|
+
call_ai_with_fallbacks() {
|
|
220
|
+
call_ai_with_round_robin "$@"
|
|
221
|
+
}
|
package/lib/config.sh
CHANGED
|
@@ -49,6 +49,18 @@ DEFAULT_AUTO_IDEATE=true
|
|
|
49
49
|
# Default retry value
|
|
50
50
|
DEFAULT_MAX_RETRIES=3
|
|
51
51
|
|
|
52
|
+
# Default LLM CLI configuration (using eval for compatibility)
|
|
53
|
+
declare -a DEFAULT_LLM_CLI_KEYS
|
|
54
|
+
declare -a DEFAULT_LLM_CLI_VALUES
|
|
55
|
+
DEFAULT_LLM_CLI_KEYS=(o3 codex gemini opus sonnet)
|
|
56
|
+
DEFAULT_LLM_CLI_VALUES[0]='codex exec -m o3 --dangerously-bypass-approvals-and-sandbox "{{PROMPT}}"'
|
|
57
|
+
DEFAULT_LLM_CLI_VALUES[1]='codex exec --dangerously-bypass-approvals-and-sandbox "{{PROMPT}}"'
|
|
58
|
+
DEFAULT_LLM_CLI_VALUES[2]='gemini -y -p "{{PROMPT}}"'
|
|
59
|
+
DEFAULT_LLM_CLI_VALUES[3]='claude --dangerously-skip-permissions --model opus -p "{{PROMPT}}"'
|
|
60
|
+
DEFAULT_LLM_CLI_VALUES[4]='claude --dangerously-skip-permissions --model sonnet -p "{{PROMPT}}"'
|
|
61
|
+
DEFAULT_LLM_RUN="sonnet gemini"
|
|
62
|
+
DEFAULT_LLM_IDEATE="opus o3"
|
|
63
|
+
|
|
52
64
|
# Load configuration from config file
|
|
53
65
|
load_config() {
|
|
54
66
|
# Accept config file path as parameter
|
|
@@ -84,12 +96,25 @@ load_config() {
|
|
|
84
96
|
# Set retry default
|
|
85
97
|
MAX_RETRIES="$DEFAULT_MAX_RETRIES"
|
|
86
98
|
|
|
99
|
+
# Set LLM CLI defaults (compatibility for older bash)
|
|
100
|
+
# Initialize associative array for LLM commands
|
|
101
|
+
# Use simpler approach for compatibility
|
|
102
|
+
LLM_CLI_o3='codex exec -m o3 --dangerously-bypass-approvals-and-sandbox "{{PROMPT}}"'
|
|
103
|
+
LLM_CLI_codex='codex exec --dangerously-bypass-approvals-and-sandbox "{{PROMPT}}"'
|
|
104
|
+
LLM_CLI_gemini='gemini -y -p "{{PROMPT}}"'
|
|
105
|
+
LLM_CLI_opus='claude --dangerously-skip-permissions --model opus -p "{{PROMPT}}"'
|
|
106
|
+
LLM_CLI_sonnet='claude --dangerously-skip-permissions --model sonnet -p "{{PROMPT}}"'
|
|
107
|
+
LLM_RUN="$DEFAULT_LLM_RUN"
|
|
108
|
+
LLM_IDEATE="$DEFAULT_LLM_IDEATE"
|
|
109
|
+
|
|
87
110
|
# Load config if found
|
|
88
111
|
if [[ -f "$config_file" ]]; then
|
|
89
112
|
echo "[DEBUG] Loading configuration from: $config_file" >&2
|
|
90
113
|
# Simple YAML parsing for key: value pairs and nested structures
|
|
91
114
|
local in_ideation_section=false
|
|
92
115
|
local in_parallel_section=false
|
|
116
|
+
local in_llm_cli_section=false
|
|
117
|
+
local llm_cli_subsection=""
|
|
93
118
|
while IFS='' read -r line; do
|
|
94
119
|
# Skip comments and empty lines
|
|
95
120
|
[[ $line =~ ^[[:space:]]*# ]] || [[ -z $line ]] && continue
|
|
@@ -110,6 +135,11 @@ load_config() {
|
|
|
110
135
|
key=$(echo "$key" | sed 's/^[[:space:]]*//;s/[[:space:]]*$//')
|
|
111
136
|
value=$(echo "$value" | sed 's/^[[:space:]]*//;s/[[:space:]]*$//')
|
|
112
137
|
|
|
138
|
+
# Debug before comment removal
|
|
139
|
+
if [[ "${DEBUG_CONFIG:-}" == "true" ]]; then
|
|
140
|
+
echo "[CONFIG DEBUG] Before comment removal: key='$key' value='$value'" >&2
|
|
141
|
+
fi
|
|
142
|
+
|
|
113
143
|
# Remove inline comments from value
|
|
114
144
|
value=$(echo "$value" | sed 's/[[:space:]]*#.*$//')
|
|
115
145
|
|
|
@@ -120,15 +150,25 @@ load_config() {
|
|
|
120
150
|
if [[ $key == "ideation_strategies" ]]; then
|
|
121
151
|
in_ideation_section=true
|
|
122
152
|
in_parallel_section=false
|
|
153
|
+
in_llm_cli_section=false
|
|
123
154
|
continue
|
|
124
155
|
elif [[ $key == "parallel" ]]; then
|
|
125
156
|
in_parallel_section=true
|
|
126
157
|
in_ideation_section=false
|
|
158
|
+
in_llm_cli_section=false
|
|
127
159
|
continue
|
|
128
|
-
elif [[ $
|
|
160
|
+
elif [[ $key == "llm_cli" ]]; then
|
|
161
|
+
in_llm_cli_section=true
|
|
162
|
+
in_ideation_section=false
|
|
163
|
+
in_parallel_section=false
|
|
164
|
+
llm_cli_subsection=""
|
|
165
|
+
continue
|
|
166
|
+
elif [[ $is_indented == false ]] && [[ $in_ideation_section == true || $in_parallel_section == true || $in_llm_cli_section == true ]]; then
|
|
129
167
|
# Non-indented key found while in a section, exit nested sections
|
|
130
168
|
in_ideation_section=false
|
|
131
169
|
in_parallel_section=false
|
|
170
|
+
in_llm_cli_section=false
|
|
171
|
+
llm_cli_subsection=""
|
|
132
172
|
fi
|
|
133
173
|
|
|
134
174
|
if [[ $in_ideation_section == true ]]; then
|
|
@@ -149,6 +189,26 @@ load_config() {
|
|
|
149
189
|
max_workers) MAX_WORKERS="$value" ;;
|
|
150
190
|
lock_timeout) LOCK_TIMEOUT="$value" ;;
|
|
151
191
|
esac
|
|
192
|
+
elif [[ $in_llm_cli_section == true ]]; then
|
|
193
|
+
# Handle indented keys in llm_cli section
|
|
194
|
+
# Check if this is a model definition (o3, codex, gemini, etc.) or a command list (run, ideate)
|
|
195
|
+
if [[ $key == "run" || $key == "ideate" ]]; then
|
|
196
|
+
# Command list - value is a space-separated list of models
|
|
197
|
+
case $key in
|
|
198
|
+
run) LLM_RUN="$value" ;;
|
|
199
|
+
ideate) LLM_IDEATE="$value" ;;
|
|
200
|
+
esac
|
|
201
|
+
else
|
|
202
|
+
# Model definition - key is model name, value is command template
|
|
203
|
+
# Remove single quotes from value if present
|
|
204
|
+
value=$(echo "$value" | sed "s/^'//;s/'$//")
|
|
205
|
+
# Debug config loading
|
|
206
|
+
if [[ "${DEBUG_CONFIG:-}" == "true" ]]; then
|
|
207
|
+
echo "[CONFIG DEBUG] Setting LLM_CLI_${key} = '$value'" >&2
|
|
208
|
+
fi
|
|
209
|
+
# Use dynamic variable name for compatibility
|
|
210
|
+
eval "LLM_CLI_${key}=\"$value\""
|
|
211
|
+
fi
|
|
152
212
|
else
|
|
153
213
|
# Handle top-level keys
|
|
154
214
|
case $key in
|
|
@@ -256,4 +316,14 @@ show_config() {
|
|
|
256
316
|
echo " Lock timeout: $LOCK_TIMEOUT"
|
|
257
317
|
echo " Auto ideate: $AUTO_IDEATE"
|
|
258
318
|
echo " Max retries: $MAX_RETRIES"
|
|
319
|
+
echo " LLM configuration:"
|
|
320
|
+
# Show LLM configurations using dynamic variable names
|
|
321
|
+
for model in o3 codex gemini opus sonnet; do
|
|
322
|
+
var_name="LLM_CLI_${model}"
|
|
323
|
+
if [[ -n "${!var_name}" ]]; then
|
|
324
|
+
echo " $model: ${!var_name}"
|
|
325
|
+
fi
|
|
326
|
+
done
|
|
327
|
+
echo " LLM for run: $LLM_RUN"
|
|
328
|
+
echo " LLM for ideate: $LLM_IDEATE"
|
|
259
329
|
}
|
package/package.json
CHANGED
package/templates/config.yaml
CHANGED
|
@@ -55,4 +55,20 @@ parallel:
|
|
|
55
55
|
max_workers: 4
|
|
56
56
|
|
|
57
57
|
# Timeout in seconds when waiting for CSV locks
|
|
58
|
-
lock_timeout: 30
|
|
58
|
+
lock_timeout: 30
|
|
59
|
+
|
|
60
|
+
# LLM/AI CLI configuration
|
|
61
|
+
llm_cli:
|
|
62
|
+
# How to run each CLI for each LLM option
|
|
63
|
+
# {{PROMPT}} will be replaced with the actual prompt text
|
|
64
|
+
o3: 'codex exec -m o3 --dangerously-bypass-approvals-and-sandbox "{{PROMPT}}"'
|
|
65
|
+
codex: 'codex exec --dangerously-bypass-approvals-and-sandbox "{{PROMPT}}"'
|
|
66
|
+
gemini: 'gemini -y -p "{{PROMPT}}"'
|
|
67
|
+
opus: 'claude --dangerously-skip-permissions --model opus -p "{{PROMPT}}"'
|
|
68
|
+
sonnet: 'claude --dangerously-skip-permissions --model sonnet -p "{{PROMPT}}"'
|
|
69
|
+
|
|
70
|
+
# What to run for each sub-command
|
|
71
|
+
# Models are tried in order, with round-robin distribution across candidates
|
|
72
|
+
# You can repeat models for weighted selection (e.g., "sonnet sonnet gemini" for 2:1 ratio)
|
|
73
|
+
run: sonnet gemini
|
|
74
|
+
ideate: opus o3
|