-
Notifications
You must be signed in to change notification settings - Fork 85
harness: default model for launches + --help regroup (release 0.5.4) #74
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change | ||||
|---|---|---|---|---|---|---|
| @@ -0,0 +1,75 @@ | ||||||
| /** | ||||||
| * @file cmd_default_models.cpp | ||||||
| * @brief `wally default-models` — read, set, or clear the model a harness launch | ||||||
| * uses when the reader gives no `-m`. See `config/preferences.h` for the | ||||||
| * resolution rule. | ||||||
| */ | ||||||
|
|
||||||
| #include "commands/commands.h" | ||||||
|
|
||||||
| #include <memory> | ||||||
| #include <string> | ||||||
|
|
||||||
| #include "config/preferences.h" | ||||||
| #include "io/output.h" | ||||||
|
|
||||||
| namespace wally::commands { | ||||||
|
|
||||||
| std::string ResolveDefaultModel(const std::string& explicit_model) { | ||||||
| const std::string effective = prefs::ResolveModel(explicit_model); | ||||||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win 🔎 Supported by static analysis🏁 Script executed: #!/bin/bash
set -euo pipefail
rg -n -C 2 \
'performance-no-automatic-move|warnings-as-errors|Werror|clang-tidy' \
-g 'CMakeLists.txt' -g '*.cmake' -g '*.yml' -g '*.yaml' .Repository: RunanywhereAI/wally Length of output: 157 🏁 Script executed: #!/bin/bash
set -euo pipefail
printf '%s\n' '--- target file ---'
cat -n src/commands/cmd_default_models.cpp
printf '%s\n' '--- nearby declarations and call sites ---'
rg -n -C 3 \
'ResolveDefaultModel|ResolveModel|SetDefaultModel|ClearDefaultModel|default-models|clear' \
src include CMakeLists.txt '*.cmake' 2>/dev/null || trueRepository: RunanywhereAI/wally Length of output: 26393 🏁 Script executed: #!/bin/bash
set -euo pipefail
printf '%s\n' '--- tracked build and lint configuration files ---'
git ls-files | rg '(^|/)(CMakeLists\.txt|[^/]+\.cmake|[^/]+\.(yml|yaml))$' | sort
printf '%s\n' '--- exact lint policy references ---'
rg -n -C 3 \
'performance-no-automatic-move|warnings-as-errors|Werror|clang-tidy|clang-tidy-checks' \
. \
-g 'CMakeLists.txt' -g '*.cmake' -g '*.yml' -g '*.yaml' \
|| trueRepository: RunanywhereAI/wally Length of output: 561 Remove
Proposed fix- const std::string effective = prefs::ResolveModel(explicit_model);
+ std::string effective = prefs::ResolveModel(explicit_model);📝 Committable suggestion
Suggested change
🤖 Prompt for AI AgentsSource: Linters/SAST tools |
||||||
| // Announce only when a default filled in for an omitted -m, so the launch | ||||||
| // never silently picks a model the reader did not name. | ||||||
| if (explicit_model.empty() && !effective.empty()) { | ||||||
| out::status_line("using default model " + effective); | ||||||
| } | ||||||
| return effective; | ||||||
| } | ||||||
|
|
||||||
| void register_default_models(CLI::App& app, GlobalOptions& options) { | ||||||
| static_cast<void>(options); | ||||||
| auto model = std::make_shared<std::string>(); | ||||||
| auto clear = std::make_shared<bool>(false); | ||||||
|
|
||||||
| CLI::App* cmd = app.add_subcommand( | ||||||
| "default-models", "Set the model a harness uses when you pass no -m"); | ||||||
| cmd->add_option("model", *model, "a model id to make the default, e.g. glm-5.3-flash"); | ||||||
| cmd->add_flag("--clear", *clear, "remove the saved default"); | ||||||
|
|
||||||
| cmd->callback([model, clear] { | ||||||
| if (*clear) { | ||||||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win Reject
Proposed fix cmd->callback([model, clear] {
+ if (*clear && !model->empty()) {
+ out::error_line("model and --clear cannot be used together");
+ throw CLI::RuntimeError(2);
+ }
if (*clear) {🤖 Prompt for AI Agents |
||||||
| std::string error; | ||||||
| if (!prefs::ClearDefaultModel(&error)) { | ||||||
| out::error_line(error); | ||||||
| throw CLI::RuntimeError(1); | ||||||
| } | ||||||
| out::status_line("default model cleared"); | ||||||
| return; | ||||||
| } | ||||||
|
|
||||||
| if (!model->empty()) { | ||||||
| std::string error; | ||||||
| if (!prefs::SetDefaultModel(*model, &error)) { | ||||||
| out::error_line(error); | ||||||
| throw CLI::RuntimeError(1); | ||||||
| } | ||||||
| out::status_line("default model set to " + *model); | ||||||
| return; | ||||||
| } | ||||||
|
|
||||||
| // No argument: report what is in effect and why. | ||||||
| const prefs::DefaultModel current = prefs::EffectiveDefaultModel(); | ||||||
| switch (current.source) { | ||||||
| case prefs::DefaultModelSource::Environment: | ||||||
| out::result_line(current.id + " (from WALLY_DEFAULT_MODEL)"); | ||||||
| break; | ||||||
| case prefs::DefaultModelSource::File: | ||||||
| out::result_line(current.id); | ||||||
| break; | ||||||
| case prefs::DefaultModelSource::None: | ||||||
| out::status_line("no default model set; pass one to save it"); | ||||||
| break; | ||||||
| } | ||||||
| }); | ||||||
| } | ||||||
|
|
||||||
| } // namespace wally::commands | ||||||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,163 @@ | ||
| #include "config/preferences.h" | ||
|
|
||
| #include <cstdio> | ||
| #include <cstdlib> | ||
| #include <filesystem> | ||
| #include <fstream> | ||
| #include <string> | ||
| #include <system_error> | ||
|
|
||
| #include <nlohmann/json.hpp> | ||
|
|
||
| #include "account/credentials.h" | ||
| #include "harness/harness.h" | ||
| #include "io/output.h" | ||
|
|
||
| namespace wally::prefs { | ||
| namespace { | ||
|
|
||
| namespace fs = std::filesystem; | ||
| using Json = nlohmann::json; | ||
|
|
||
| constexpr const char* kFileName = "preferences.json"; | ||
| constexpr const char* kDefaultModelKey = "default_model"; | ||
|
|
||
| std::string EnvValue(const char* name) { | ||
| const char* value = std::getenv(name); | ||
| return value == nullptr ? std::string() : std::string(value); | ||
| } | ||
|
|
||
| /// Reads and parses the preferences file. Returns an empty object for a missing | ||
| /// file, and — on a parse error — an empty object plus a stderr warning, so a | ||
| /// corrupt file degrades to "no preferences" rather than breaking a launch. | ||
| Json ReadFile() { | ||
| const std::string path = PreferencesPath(); | ||
| if (path.empty()) { | ||
| return Json::object(); | ||
| } | ||
| std::ifstream file(path, std::ios::binary); | ||
| if (!file.good()) { | ||
| return Json::object(); | ||
| } | ||
| Json parsed = Json::parse(file, nullptr, /*allow_exceptions=*/false); | ||
| if (parsed.is_discarded() || !parsed.is_object()) { | ||
| out::status_line("ignoring unreadable preferences file at " + path); | ||
| return Json::object(); | ||
| } | ||
| return parsed; | ||
| } | ||
|
|
||
| /// Writes `document` to the preferences file atomically: a sibling temp file | ||
| /// then a rename, so a reader never sees a half-written file. | ||
| bool WriteFile(const Json& document, std::string* error) { | ||
| const std::string path = PreferencesPath(); | ||
| if (path.empty()) { | ||
| if (error != nullptr) { | ||
| *error = "cannot resolve a home directory for preferences"; | ||
| } | ||
| return false; | ||
| } | ||
|
|
||
| const fs::path target(path); | ||
| std::error_code ec; | ||
| fs::create_directories(target.parent_path(), ec); | ||
| if (ec) { | ||
| if (error != nullptr) { | ||
| *error = "cannot create " + target.parent_path().string() + ": " + ec.message(); | ||
| } | ||
| return false; | ||
| } | ||
|
|
||
| const fs::path temp = target.parent_path() / (std::string(kFileName) + ".tmp"); | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🗄️ Data Integrity & Integration | 🟠 Major | 🏗️ Heavy lift Use a unique temporary file and lock the full update transaction. Every write uses Use a per-profile inter-process lock from 🤖 Prompt for AI Agents |
||
| { | ||
| std::ofstream out(temp, std::ios::binary | std::ios::trunc); | ||
| if (!out.good()) { | ||
| if (error != nullptr) { | ||
| *error = "cannot write " + temp.string(); | ||
| } | ||
| return false; | ||
| } | ||
| out << document.dump(2) << '\n'; | ||
| out.flush(); | ||
| if (!out.good()) { | ||
| if (error != nullptr) { | ||
| *error = "failed writing " + temp.string(); | ||
| } | ||
| return false; | ||
| } | ||
| } | ||
|
|
||
| fs::rename(temp, target, ec); | ||
| if (ec) { | ||
| fs::remove(temp, ec); | ||
| if (error != nullptr) { | ||
| *error = "cannot replace " + target.string() + ": " + ec.message(); | ||
| } | ||
| return false; | ||
| } | ||
| return true; | ||
| } | ||
|
|
||
| } // namespace | ||
|
|
||
| std::string PreferencesPath() { | ||
| const std::string directory = account::ProfileDirectory(); | ||
| if (directory.empty()) { | ||
| return {}; | ||
| } | ||
| return (fs::path(directory) / kFileName).string(); | ||
| } | ||
|
|
||
| std::optional<std::string> FileDefaultModel() { | ||
| const Json document = ReadFile(); | ||
| const auto it = document.find(kDefaultModelKey); | ||
| if (it == document.end() || !it->is_string()) { | ||
| return std::nullopt; | ||
| } | ||
| const auto value = it->get<std::string>(); | ||
| if (value.empty()) { | ||
| return std::nullopt; | ||
| } | ||
| return value; | ||
| } | ||
|
|
||
| DefaultModel EffectiveDefaultModel() { | ||
| const std::string env = EnvValue("WALLY_DEFAULT_MODEL"); | ||
| if (!env.empty()) { | ||
| return {env, DefaultModelSource::Environment}; | ||
| } | ||
| if (const std::optional<std::string> file = FileDefaultModel()) { | ||
| return {*file, DefaultModelSource::File}; | ||
| } | ||
| return {}; | ||
| } | ||
|
|
||
| std::string ResolveModel(const std::string& explicit_model) { | ||
| if (!explicit_model.empty()) { | ||
| return explicit_model; | ||
| } | ||
| return EffectiveDefaultModel().id; | ||
| } | ||
|
|
||
| bool SetDefaultModel(const std::string& id, std::string* error) { | ||
| if (!harness::ModelIdIsSafe(id)) { | ||
| if (error != nullptr) { | ||
| *error = "not a usable model id: " + id; | ||
| } | ||
| return false; | ||
| } | ||
| Json document = ReadFile(); | ||
| document[kDefaultModelKey] = id; | ||
| return WriteFile(document, error); | ||
| } | ||
|
|
||
| bool ClearDefaultModel(std::string* error) { | ||
| Json document = ReadFile(); | ||
| if (!document.contains(kDefaultModelKey)) { | ||
| return true; | ||
| } | ||
| document.erase(kDefaultModelKey); | ||
| return WriteFile(document, error); | ||
| } | ||
|
|
||
| } // namespace wally::prefs | ||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🩺 Stability & Availability | 🟠 Major | ⚡ Quick win
Do not publish the formula with a placeholder artifact.
The changed URL targets an asset that the comment states is not published. The all-zero SHA-256 then causes Homebrew verification to fail. Publish the archive and update the formula with its real SHA-256 before this version is released.
🤖 Prompt for AI Agents