namespace LlamaApp.Common;
///
/// Policy for the free-form "custom serve arguments" setting. llama.cpp honors
/// the last occurrence of a repeated flag, and the custom tokens are appended
/// after the app's own — so without this an entry like --host 0.0.0.0
/// would silently override the validated listen address (and the Settings UI
/// would keep claiming "Localhost"). The app owns the flags that define the
/// bind, the auth key, and the per-model preset, so those are reserved.
///
/// Also provides redaction for the launch log: some llama.cpp flags take
/// a secret value, and the raw custom text is otherwise echoed verbatim into
/// %LOCALAPPDATA%\Llama\logs.
///
public static class ServeArgumentPolicy
{
///
/// Flags the app sets itself and refuses in custom arguments. --host
/// and --port define the reachable endpoint; --api-key the
/// app-managed auth; --models-preset the per-model context file;
/// --models-dir the "Open With" local-models folder; the
/// --ssl* flags change the transport (and would break the
/// loopback client's assumptions).
///
/// Also the secret-bearing flags: custom arguments become process
/// argv, where a value leaks into command-line auditing (Event Log 4688),
/// EDR telemetry, crash dumps and process listings. The app's own
/// channels exist precisely to keep secrets off argv — the HF token
/// travels as the server's HF_TOKEN environment variable, the API key as
/// --api-key-file, the key password as --ssl-key-pass's
/// file-based counterpart — so a custom-args copy of any of them is
/// refused (matching ).
///
public static readonly IReadOnlyList ReservedFlags = new[]
{
"--host",
"--port",
"--api-key",
"--api-key-file",
"--models-preset",
"--models-dir",
"--ssl",
"--ssl-key-file",
"--ssl-cert-file",
"--hf-token",
"--ssl-key-pass",
};
/// Flags whose value must never be written to the log.
public static readonly IReadOnlyList SecretFlags = new[]
{
"--api-key",
"--api-key-file",
"--hf-token",
"--ssl-key-file",
"--ssl-key-pass",
};
///
/// Returns a user-facing error when contains a
/// reserved flag, or null when the arguments are acceptable.
///
public static string? Validate(IReadOnlyList tokens)
=> FindReservedFlag(tokens) is { } flag
? $"{flag} is managed by Llama and can't be used in custom arguments."
: null;
/// The first reserved flag present, matched as --flag or --flag=value (case-insensitive).
public static string? FindReservedFlag(IReadOnlyList tokens)
{
foreach (var token in tokens)
foreach (var flag in ReservedFlags)
if (IsFlag(token, flag)) return flag;
return null;
}
///
/// Renders tokens for logging with secret-flag values replaced by
/// *** — both the --flag value and --flag=value forms.
///
public static string RedactForLog(IEnumerable tokens)
{
var output = new List();
var redactNext = false;
foreach (var token in tokens)
{
if (redactNext)
{
output.Add("***");
redactNext = false;
continue;
}
var secret = SecretFlags.FirstOrDefault(f => IsFlag(token, f));
if (secret is null)
{
output.Add(token);
continue;
}
// "--flag=value" already carries the value; "--flag value" takes
// the next token.
if (token.Length > secret.Length && token[secret.Length] == '=')
output.Add($"{secret}=***");
else
{
output.Add(token);
redactNext = true;
}
}
return string.Join(' ', output);
}
private static bool IsFlag(string token, string flag)
=> token.Equals(flag, StringComparison.OrdinalIgnoreCase)
|| (token.Length > flag.Length
&& token.StartsWith(flag, StringComparison.OrdinalIgnoreCase)
&& token[flag.Length] == '=');
}