Skip to content

Commit 1efd475

Browse files
committed
Merge branch 'main' of github.com:emagedoc/CodeClash
2 parents 8dc4d81 + 4f81670 commit 1efd475

16 files changed

Lines changed: 310 additions & 41 deletions

aws/run_job.py

Lines changed: 2 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -21,20 +21,12 @@
2121

2222
import boto3
2323

24-
from codeclash.utils.git_utils import get_current_git_branch, has_unpushed_commits, is_git_repo_dirty
24+
from codeclash.utils.git_utils import get_current_git_branch
2525
from codeclash.utils.log import get_logger
2626

2727
logger = get_logger("launch", emoji="🚀")
2828

2929

30-
def check_git_status_and_confirm() -> None:
31-
if not is_git_repo_dirty() and not has_unpushed_commits():
32-
return
33-
34-
if input("Git repository dirty/unpushed, continue anyway? (y/N): ").strip().lower() not in ("y", "yes"):
35-
sys.exit(1)
36-
37-
3830
class AWSBatchJobLauncher:
3931
def __init__(
4032
self,
@@ -167,7 +159,7 @@ def main():
167159
parser.add_argument("--region", default="us-east-1", help="AWS region (default: us-east-1)")
168160
parser.add_argument("--wait", action="store_true", help="Wait for the job to complete before exiting")
169161
parser.add_argument("--show-logs", action="store_true", help="Show job logs after completion (implies --wait)")
170-
parser.add_argument("-y", action="store_true", help="Skip git dirty prompt and continue automatically")
162+
parser.add_argument("-y", action="store_true", help="Legacy")
171163

172164
if "--" in sys.argv:
173165
separator_index = sys.argv.index("--")
@@ -178,9 +170,6 @@ def main():
178170

179171
args = parser.parse_args(aws_args)
180172

181-
if not args.y:
182-
check_git_status_and_confirm()
183-
184173
launcher = AWSBatchJobLauncher(
185174
job_definition_name=args.job_definition, job_queue=args.job_queue, region=args.region
186175
)

aws/setup/docker/build_and_push_all.sh

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ set -euo pipefail
44

55
THIS_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
66
BUILD_SCRIPT="$THIS_DIR/build_and_push_aws.sh"
7-
GAMES_DOCKER_DIR="$THIS_DIR/../../docker"
7+
GAMES_DOCKER_DIR="$THIS_DIR/../../../docker"
88

99
# AWSCodeClash -> codeclash
1010
"$BUILD_SCRIPT" AWSCodeClash.Dockerfile codeclash "$THIS_DIR"
@@ -17,3 +17,4 @@ GAMES_DOCKER_DIR="$THIS_DIR/../../docker"
1717
"$BUILD_SCRIPT" "$GAMES_DOCKER_DIR/HuskyBench.Dockerfile" codeclash/huskybench "$GAMES_DOCKER_DIR"
1818
"$BUILD_SCRIPT" "$GAMES_DOCKER_DIR/RoboCode.Dockerfile" codeclash/robocode "$GAMES_DOCKER_DIR"
1919
"$BUILD_SCRIPT" "$GAMES_DOCKER_DIR/RobotRumble.Dockerfile" codeclash/robotrumble "$GAMES_DOCKER_DIR"
20+
"$BUILD_SCRIPT" "$GAMES_DOCKER_DIR/Halite.Dockerfile" codeclash/halite "$GAMES_DOCKER_DIR"

codeclash/agents/minisweagent.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,11 +10,13 @@
1010
from minisweagent.models.test_models import DeterministicModel
1111
from minisweagent.run.utils.save import save_traj
1212

13+
from codeclash import REPO_DIR
1314
from codeclash.agents.player import Player
1415
from codeclash.agents.utils import GameContext
1516
from codeclash.utils.environment import copy_to_container
1617

1718
os.environ["MSWEA_MODEL_RETRY_STOP_AFTER_ATTEMPT"] = "90"
19+
os.environ["LITELLM_MODEL_REGISTRY_PATH"] = str((REPO_DIR / "configs" / "litellm_custom_model_config.yaml").resolve())
1820

1921

2022
class ClashAgent(DefaultAgent):

codeclash/viewer/app.py

Lines changed: 80 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1409,4 +1409,84 @@ def compute():
14091409
return jsonify({"success": False, "error": str(e)}), 500
14101410

14111411

1412+
@app.route("/picker/api/guess-config-names", methods=["POST"])
1413+
@print_timing
1414+
def guess_config_names():
1415+
"""Guess config file names based on metadata.json files"""
1416+
try:
1417+
data = request.get_json()
1418+
folder_paths = data.get("folder_paths", [])
1419+
1420+
if not folder_paths:
1421+
return jsonify({"success": False, "error": "No folder paths provided"})
1422+
1423+
results = []
1424+
for folder_path in folder_paths:
1425+
folder_path_obj = LOG_BASE_DIR / folder_path
1426+
1427+
if not folder_path_obj.exists() or not is_game_folder(folder_path_obj):
1428+
results.append({"folder": folder_path, "config_name": None, "error": "Invalid folder"})
1429+
continue
1430+
1431+
metadata = load_metadata(folder_path_obj)
1432+
if not metadata.is_valid:
1433+
results.append({"folder": folder_path, "config_name": None, "error": "No metadata"})
1434+
continue
1435+
1436+
# Extract components for config name
1437+
game_name = metadata.game_name
1438+
rounds = metadata.get_path("config.tournament.rounds")
1439+
sims = metadata.get_path("config.game.sims_per_round")
1440+
players = metadata.get_path("config.players", [])
1441+
1442+
# Extract player names
1443+
player_names = []
1444+
for player in players:
1445+
if isinstance(player, dict):
1446+
name = player.get("name", "")
1447+
if name:
1448+
player_names.append(name)
1449+
1450+
# Build config file name
1451+
if game_name and rounds and sims and len(player_names) >= 2:
1452+
# Determine correct model ordering by checking folder name
1453+
folder_name = Path(folder_path).name
1454+
model1, model2 = player_names[0], player_names[1]
1455+
1456+
# Check which order appears in the folder name
1457+
if f"{model1}.{model2}" in folder_name:
1458+
# Order is correct
1459+
pass
1460+
elif f"{model2}.{model1}" in folder_name:
1461+
# Swap order
1462+
model1, model2 = model2, model1
1463+
# If neither appears, keep original order
1464+
1465+
config_name = f"{game_name}__{model1}__{model2}__r{rounds}__s{sims}.yaml"
1466+
results.append({"folder": folder_path, "config_name": config_name, "error": None})
1467+
else:
1468+
missing = []
1469+
if not game_name:
1470+
missing.append("game_name")
1471+
if not rounds:
1472+
missing.append("rounds")
1473+
if not sims:
1474+
missing.append("sims")
1475+
if len(player_names) < 2:
1476+
missing.append(f"players (found {len(player_names)})")
1477+
results.append(
1478+
{
1479+
"folder": folder_path,
1480+
"config_name": None,
1481+
"error": f"Missing: {', '.join(missing)}",
1482+
}
1483+
)
1484+
1485+
return jsonify({"success": True, "results": results})
1486+
1487+
except Exception as e:
1488+
logger.error(f"Error guessing config names: {e}", exc_info=True)
1489+
return jsonify({"success": False, "error": str(e)}), 500
1490+
1491+
14121492
# Use run_viewer.py to launch the application

codeclash/viewer/static/js/picker.js

Lines changed: 151 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -373,6 +373,157 @@ function fillTextareaWithAWSResubmitCommands() {
373373
}
374374
}
375375

376+
async function fillTextareaWithGuessedConfigNames() {
377+
const selectedCheckboxes = getVisibleCheckedCheckboxes();
378+
379+
if (selectedCheckboxes.length === 0) {
380+
showModalWarning(
381+
"No folders selected. Please select folders from the table first.",
382+
);
383+
document.getElementById("bulk-actions-textarea").value = "";
384+
return;
385+
}
386+
387+
// Show loading message
388+
const textarea = document.getElementById("bulk-actions-textarea");
389+
textarea.value = "Loading...";
390+
hideModalWarning();
391+
392+
// Extract folder paths
393+
const folderPaths = Array.from(selectedCheckboxes).map((checkbox) =>
394+
checkbox.getAttribute("data-path"),
395+
);
396+
397+
try {
398+
// Call backend API
399+
const response = await fetch("/picker/api/guess-config-names", {
400+
method: "POST",
401+
headers: {
402+
"Content-Type": "application/json",
403+
},
404+
body: JSON.stringify({ folder_paths: folderPaths }),
405+
});
406+
407+
const data = await response.json();
408+
409+
if (!data.success) {
410+
showModalWarning(`Error: ${data.error}`);
411+
textarea.value = "";
412+
return;
413+
}
414+
415+
// Process results
416+
const successfulResults = data.results.filter(
417+
(result) => result.config_name,
418+
);
419+
const failedResults = data.results.filter((result) => !result.config_name);
420+
421+
if (successfulResults.length === 0) {
422+
showModalWarning("No config names could be guessed from the metadata.");
423+
textarea.value = "";
424+
return;
425+
}
426+
427+
// Format output
428+
const configNames = successfulResults.map((result) => result.config_name);
429+
textarea.value = configNames.join("\n");
430+
431+
// Show warning if some folders failed
432+
if (failedResults.length > 0) {
433+
const errorMessages = failedResults.map((result) => {
434+
const folderName = result.folder.split("/").pop();
435+
return `${folderName}: ${result.error}`;
436+
});
437+
showModalWarning(
438+
`Warning: ${failedResults.length} folder(s) failed:\n${errorMessages.join("\n")}`,
439+
);
440+
} else {
441+
hideModalWarning();
442+
}
443+
} catch (error) {
444+
console.error("Error guessing config names:", error);
445+
showModalWarning(`Error: ${error.message}`);
446+
textarea.value = "";
447+
}
448+
}
449+
450+
async function fillTextareaWithAWSSubmitCommands() {
451+
const selectedCheckboxes = getVisibleCheckedCheckboxes();
452+
453+
if (selectedCheckboxes.length === 0) {
454+
showModalWarning(
455+
"No folders selected. Please select folders from the table first.",
456+
);
457+
document.getElementById("bulk-actions-textarea").value = "";
458+
return;
459+
}
460+
461+
// Show loading message
462+
const textarea = document.getElementById("bulk-actions-textarea");
463+
textarea.value = "Loading...";
464+
hideModalWarning();
465+
466+
// Extract folder paths
467+
const folderPaths = Array.from(selectedCheckboxes).map((checkbox) =>
468+
checkbox.getAttribute("data-path"),
469+
);
470+
471+
try {
472+
// Call backend API
473+
const response = await fetch("/picker/api/guess-config-names", {
474+
method: "POST",
475+
headers: {
476+
"Content-Type": "application/json",
477+
},
478+
body: JSON.stringify({ folder_paths: folderPaths }),
479+
});
480+
481+
const data = await response.json();
482+
483+
if (!data.success) {
484+
showModalWarning(`Error: ${data.error}`);
485+
textarea.value = "";
486+
return;
487+
}
488+
489+
// Process results
490+
const successfulResults = data.results.filter(
491+
(result) => result.config_name,
492+
);
493+
const failedResults = data.results.filter((result) => !result.config_name);
494+
495+
if (successfulResults.length === 0) {
496+
showModalWarning("No config names could be guessed from the metadata.");
497+
textarea.value = "";
498+
return;
499+
}
500+
501+
// Format output as AWS submit commands
502+
const commands = successfulResults.map(
503+
(result) =>
504+
`aws/run_job.py -- aws/docker_and_sync.sh python main.py configs/main/${result.config_name}`,
505+
);
506+
textarea.value = commands.join("\n");
507+
508+
// Show warning if some folders failed
509+
if (failedResults.length > 0) {
510+
const errorMessages = failedResults.map((result) => {
511+
const folderName = result.folder.split("/").pop();
512+
return `${folderName}: ${result.error}`;
513+
});
514+
showModalWarning(
515+
`Warning: ${failedResults.length} folder(s) skipped:\n${errorMessages.join("\n")}`,
516+
);
517+
} else {
518+
hideModalWarning();
519+
}
520+
} catch (error) {
521+
console.error("Error generating AWS submit commands:", error);
522+
showModalWarning(`Error: ${error.message}`);
523+
textarea.value = "";
524+
}
525+
}
526+
376527
function copyFromModal() {
377528
const textarea = document.getElementById("bulk-actions-textarea");
378529
const text = textarea.value;

codeclash/viewer/templates/batch.html

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -676,9 +676,9 @@ <h1><i class="bi bi-cloud"></i> AWS Batch Job Monitor</h1>
676676
<div class="filter-container">
677677
<label for="time-range-filter">Time range:</label>
678678
<select id="time-range-filter" class="filter-select" onchange="applyTimeRangeFilter()">
679-
<option value="6">Last 6h</option>
679+
<option value="6" selected>Last 6h</option>
680680
<option value="12">Last 12h</option>
681-
<option value="24" selected>Last 24h</option>
681+
<option value="24">Last 24h</option>
682682
<option value="48">Last 48h</option>
683683
<option value="72">Last 72h</option>
684684
</select>
@@ -763,7 +763,7 @@ <h2>Bulk Actions</h2>
763763
let currentSort = { column: 'created_at', ascending: false };
764764
let statusFilter = '';
765765
let roundsFilter = 'any';
766-
let timeRangeHours = 24;
766+
let timeRangeHours = 6;
767767
let lastRefreshTime = null;
768768
let selectedJobs = new Set();
769769
let totalCpusRunning = 0;

codeclash/viewer/templates/picker.html

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -173,6 +173,12 @@ <h2>Bulk Actions</h2>
173173
<button class="modal-action-btn" onclick="fillTextareaWithAWSResubmitCommands()">
174174
<i class="bi bi-arrow-repeat"></i> AWS resubmit
175175
</button>
176+
<button class="modal-action-btn" onclick="fillTextareaWithGuessedConfigNames()">
177+
<i class="bi bi-file-earmark-text"></i> Guess config file name
178+
</button>
179+
<button class="modal-action-btn" onclick="fillTextareaWithAWSSubmitCommands()">
180+
<i class="bi bi-cloud-upload"></i> Guess AWS submit cmd
181+
</button>
176182
</div>
177183
<div id="modal-warning" class="modal-warning">
178184
<i class="bi bi-exclamation-triangle"></i>
Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
{
2+
"dashscope/qwen3-coder-plus-2025-09-23": {
3+
"litellm_provider": "dashscope",
4+
"max_input_tokens": 997952,
5+
"max_output_tokens": 65536,
6+
"max_tokens": 1000000,
7+
"mode": "chat",
8+
"source": "https://www.alibabacloud.com/help/en/model-studio/models",
9+
"supports_function_calling": true,
10+
"supports_reasoning": true,
11+
"supports_tool_choice": true,
12+
"tiered_pricing": [
13+
{
14+
"cache_read_input_token_cost": 1e-07,
15+
"input_cost_per_token": 1e-06,
16+
"output_cost_per_token": 5e-06,
17+
"range": [
18+
0,
19+
32000.0
20+
]
21+
},
22+
{
23+
"cache_read_input_token_cost": 1.8e-07,
24+
"input_cost_per_token": 1.8e-06,
25+
"output_cost_per_token": 9e-06,
26+
"range": [
27+
32000.0,
28+
128000.0
29+
]
30+
},
31+
{
32+
"cache_read_input_token_cost": 3e-07,
33+
"input_cost_per_token": 3e-06,
34+
"output_cost_per_token": 1.5e-05,
35+
"range": [
36+
128000.0,
37+
256000.0
38+
]
39+
},
40+
{
41+
"cache_read_input_token_cost": 6e-07,
42+
"input_cost_per_token": 6e-06,
43+
"output_cost_per_token": 6e-05,
44+
"range": [
45+
256000.0,
46+
1000000.0
47+
]
48+
}
49+
]
50+
}
51+
}

0 commit comments

Comments
 (0)