mirror of
https://github.com/garrytan/gstack.git
synced 2026-10-31 14:06:50 +01:00
B3: Codex auth probe accepts custom providers' env_key credentials
The auth probe only knew CODEX_API_KEY, OPENAI_API_KEY and auth.json, so a working self-hosted or OpenAI-compatible provider configured with [model_providers.<id>] env_key = "X" reported AUTH_FAILED and /codex stopped. The probe now reads env_key from model_providers tables only (comments ignored, the name must be a shell identifier before it is dereferenced) and accepts a set variable; when none is set it names the missing key on stderr. Ported from #2192 with the table scoping added. Co-authored-by: hardy <932821449@qq.com>
This commit is contained in:
1 parent
ee70becad5
commit
ebab87bb11
2 files changed
+60
-2
No files matched your search
+22
-2
@@ -21,8 +21,9 @@
|
||||
# --- Auth probe -------------------------------------------------------------
|
||||
|
||||
_gstack_codex_auth_probe() {
|
||||
# Multi-signal: env vars OR auth file. Avoids false negatives for env-auth
|
||||
# users (CI, platform engineers) that a file-only check would reject.
|
||||
# Multi-signal: env vars OR auth file OR a custom provider's env_key. Avoids
|
||||
# false negatives for env-auth users (CI, platform engineers, self-hosted
|
||||
# OpenAI-compatible providers) that a file-only check would reject.
|
||||
local _codex_home="${CODEX_HOME:-$HOME/.codex}"
|
||||
# Use `-n` which returns true only for non-empty non-whitespace. Bash's [ -n ]
|
||||
# alone allows whitespace; pair with a whitespace strip for robustness.
|
||||
@@ -33,7 +34,26 @@ _gstack_codex_auth_probe() {
|
||||
echo "AUTH_OK"
|
||||
return 0
|
||||
fi
|
||||
# Custom OpenAI-compatible providers (#2192): a [model_providers.<id>]
|
||||
# table names its credential variable in env_key. Only keys inside those
|
||||
# tables count, comments are ignored, and the name must be a plain shell
|
||||
# identifier before it is dereferenced.
|
||||
local _env_key _resolved _missing=""
|
||||
if [ -f "$_codex_home/config.toml" ]; then
|
||||
while IFS= read -r _env_key; do
|
||||
case "$_env_key" in ''|[0-9]*|*[!A-Za-z0-9_]*) continue ;; esac
|
||||
_resolved=$(printf '%s' "${!_env_key:-}" | tr -d '[:space:]')
|
||||
if [ -n "$_resolved" ]; then
|
||||
echo "AUTH_OK"
|
||||
return 0
|
||||
fi
|
||||
_missing="${_missing:+$_missing, }$_env_key"
|
||||
done < <(awk '/^[[:space:]]*#/ { next }
|
||||
/^[[:space:]]*\[/ { in_provider = ($0 ~ /^[[:space:]]*\[model_providers\.[^]]+\][[:space:]]*(#.*)?$/); next }
|
||||
in_provider && /^[[:space:]]*env_key[[:space:]]*=/ { v = $0; sub(/^[^=]*=[[:space:]]*/, "", v); sub(/[[:space:]]*#.*$/, "", v); gsub(/["\047[:space:]]/, "", v); print v }' "$_codex_home/config.toml" 2>/dev/null)
|
||||
fi
|
||||
echo "AUTH_FAILED"
|
||||
[ -n "$_missing" ] && echo "HINT: $_codex_home/config.toml names a custom provider key ($_missing), but it is not set in this shell. Export it, or run codex login." >&2
|
||||
return 1
|
||||
}
|
||||
|
||||
|
||||
@@ -51,6 +51,44 @@ function tempHome(): string {
|
||||
}
|
||||
|
||||
describe('gstack-codex-probe: auth probe', () => {
|
||||
// #2192: custom OpenAI-compatible providers name their credential variable
|
||||
// in config.toml [model_providers.<id>] env_key; a set variable is auth.
|
||||
const withConfig = (toml: string, env: Record<string, string | undefined> = {}) => {
|
||||
const home = tempHome();
|
||||
try {
|
||||
fs.mkdirSync(path.join(home, '.codex'), { recursive: true });
|
||||
fs.writeFileSync(path.join(home, '.codex', 'config.toml'), toml);
|
||||
return runProbe({ snippet: '_gstack_codex_auth_probe', env, home });
|
||||
} finally { fs.rmSync(home, { recursive: true, force: true }); }
|
||||
};
|
||||
const MIMO = '[model_providers.mimo]\nenv_key = "MIMO_API_KEY"\n';
|
||||
|
||||
test('#2192: a provider env_key whose variable is set → AUTH_OK', () => {
|
||||
const r = withConfig(MIMO, { MIMO_API_KEY: 'tp-test-key' });
|
||||
expect([r.stdout.trim(), r.status]).toEqual(['AUTH_OK', 0]);
|
||||
const several = withConfig('[model_providers.a]\nenv_key = "MISSING_A"\n\n[model_providers.b]\nenv_key = \'MIMO_API_KEY\' # inline comment\n', { MIMO_API_KEY: 'k' });
|
||||
expect(several.stdout.trim()).toBe('AUTH_OK');
|
||||
});
|
||||
|
||||
test('#2192: a provider env_key that is unset or blank → AUTH_FAILED naming the key', () => {
|
||||
for (const value of [undefined, ' \t\n']) {
|
||||
const r = withConfig(MIMO, { MIMO_API_KEY: value });
|
||||
expect([r.stdout.trim(), r.status]).toEqual(['AUTH_FAILED', 1]);
|
||||
expect(r.stderr).toContain('custom provider key (MIMO_API_KEY)');
|
||||
}
|
||||
});
|
||||
|
||||
test('#2192: commented env_key lines and env_key outside [model_providers.*] never count', () => {
|
||||
const toml = '[model_providers.mimo]\n# env_key = "DEPRECATED_KEY"\nenv_key = "MIMO_API_KEY"\n[profiles.work]\nenv_key = "PROFILE_KEY"\n';
|
||||
expect(withConfig(toml, { DEPRECATED_KEY: 'old', PROFILE_KEY: 'p' }).stdout.trim()).toBe('AUTH_FAILED');
|
||||
});
|
||||
|
||||
test('#2192: malformed config.toml or a non-identifier env_key → AUTH_FAILED, no crash', () => {
|
||||
expect(withConfig('\x00\x01 garbage \xff[broken\n=no=\n').stdout.trim()).toBe('AUTH_FAILED');
|
||||
const injected = withConfig('[model_providers.x]\nenv_key = "A;touch /tmp/never"\n');
|
||||
expect([injected.stdout.trim(), injected.status]).toEqual(['AUTH_FAILED', 1]);
|
||||
});
|
||||
|
||||
test('CODEX_API_KEY set → AUTH_OK', () => {
|
||||
const home = tempHome();
|
||||
try {
|
||||
|
||||
Reference in new issue
Block a user