Skip to content

Commit 5f95640

Browse files
Restore Morph secret staging for versioned devboxes
1 parent 8db64ab commit 5f95640

4 files changed

Lines changed: 167 additions & 3 deletions

File tree

.github/morph/opengauss-template.yaml

Lines changed: 85 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
name: Open Gauss Batteries Included Devbox
2-
description: Installer-driven Open Gauss template that uses the current checkout when run locally and otherwise clones math-inc/OpenGauss, runs the internal installer, exposes the local guide, and opens a ready Gauss session.
2+
description: Installer-driven Open Gauss template that uses the current checkout when run locally and otherwise clones math-inc/OpenGauss, runs the internal installer, stages optional provider keys, exposes the local guide, and opens a ready Gauss session.
33
steps:
44
- id: bootstrap-installer-runtime
55
title: Prepare Open Gauss Installer Runtime
@@ -63,6 +63,90 @@ steps:
6363
./scripts/install-internal.sh --with-workspace --noninteractive
6464
. "$HOME/.opengauss-template/runtime.env"
6565
"$HOME/.local/bin/gauss-launch-session" --print-summary || true
66+
- id: optional-openrouter
67+
title: Optionally Stage OPENROUTER_API_KEY
68+
type: exportSecret
69+
name: OPENROUTER_API_KEY
70+
optional: true
71+
run: |
72+
set -euo pipefail
73+
. "$HOME/.opengauss-template/runtime.env"
74+
mkdir -p "$GAUSS_HOME"
75+
touch "$GAUSS_HOME/.env"
76+
python3 - <<'PY'
77+
from pathlib import Path
78+
import os
79+
80+
env_path = Path(os.environ["GAUSS_HOME"]) / ".env"
81+
lines = []
82+
if env_path.exists():
83+
lines = [line for line in env_path.read_text().splitlines() if line and not line.startswith("OPENROUTER_API_KEY=")]
84+
value = os.environ.get("OPENROUTER_API_KEY")
85+
if not value:
86+
raise SystemExit(0)
87+
lines.append("OPENROUTER_API_KEY=" + value)
88+
env_path.write_text("\n".join(lines) + "\n")
89+
PY
90+
- id: optional-openai
91+
title: Optionally Stage OPENAI_API_KEY
92+
type: exportSecret
93+
name: OPENAI_API_KEY
94+
optional: true
95+
run: |
96+
set -euo pipefail
97+
. "$HOME/.opengauss-template/runtime.env"
98+
mkdir -p "$GAUSS_HOME"
99+
touch "$GAUSS_HOME/.env"
100+
python3 - <<'PY'
101+
from pathlib import Path
102+
import os
103+
104+
env_path = Path(os.environ["GAUSS_HOME"]) / ".env"
105+
lines = []
106+
if env_path.exists():
107+
lines = [line for line in env_path.read_text().splitlines() if line and not line.startswith("OPENAI_API_KEY=")]
108+
value = os.environ.get("OPENAI_API_KEY")
109+
if not value:
110+
raise SystemExit(0)
111+
lines.append("OPENAI_API_KEY=" + value)
112+
env_path.write_text("\n".join(lines) + "\n")
113+
PY
114+
- id: optional-anthropic
115+
title: Optionally Stage ANTHROPIC_API_KEY
116+
type: exportSecret
117+
name: ANTHROPIC_API_KEY
118+
optional: true
119+
run: |
120+
set -euo pipefail
121+
. "$HOME/.opengauss-template/runtime.env"
122+
mkdir -p "$GAUSS_HOME"
123+
touch "$GAUSS_HOME/.env"
124+
python3 - <<'PY'
125+
from pathlib import Path
126+
import os
127+
128+
env_path = Path(os.environ["GAUSS_HOME"]) / ".env"
129+
lines = []
130+
if env_path.exists():
131+
lines = [line for line in env_path.read_text().splitlines() if line and not line.startswith("ANTHROPIC_API_KEY=")]
132+
value = os.environ.get("ANTHROPIC_API_KEY")
133+
if not value:
134+
raise SystemExit(0)
135+
lines.append("ANTHROPIC_API_KEY=" + value)
136+
env_path.write_text("\n".join(lines) + "\n")
137+
PY
138+
- id: finalize-provider-selection
139+
title: Apply Main Provider Selection
140+
type: command
141+
run: |
142+
set -euo pipefail
143+
. "$HOME/.opengauss-template/runtime.env"
144+
if [ -f "$GAUSS_HOME/.env" ]; then
145+
set -a
146+
. "$GAUSS_HOME/.env"
147+
set +a
148+
fi
149+
"$HOME/.local/bin/gauss-configure-main-provider" auto || true
66150
- id: start-guide-server
67151
title: Start Guide Iframe Server
68152
type: command

cli.py

Lines changed: 19 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1730,7 +1730,25 @@ def _ensure_runtime_credentials(self) -> bool:
17301730
resolved_provider = runtime.get("provider", "openrouter")
17311731
resolved_api_mode = runtime.get("api_mode", self.api_mode)
17321732
if not isinstance(api_key, str) or not api_key:
1733-
self.console.print("[bold red]Provider resolver returned an empty API key.[/]")
1733+
if resolved_provider in {"openrouter", "custom"}:
1734+
message = (
1735+
"No main interactive provider is configured yet. "
1736+
"Run `gauss setup`, or save `OPENROUTER_API_KEY`, `OPENAI_API_KEY`, "
1737+
"or `ANTHROPIC_API_KEY` in `~/.gauss/.env`."
1738+
)
1739+
elif resolved_provider == "anthropic":
1740+
message = (
1741+
"No Anthropic credentials are available for chat. "
1742+
"Run `gauss setup`, `claude auth login`, or save `ANTHROPIC_API_KEY`."
1743+
)
1744+
elif resolved_provider == "openai-codex":
1745+
message = (
1746+
"No Codex credentials are available for chat. "
1747+
"Run `gauss setup`, `codex login`, or save `OPENAI_API_KEY`."
1748+
)
1749+
else:
1750+
message = "Provider resolver returned an empty API key."
1751+
self.console.print(f"[bold red]{message}[/]")
17341752
return False
17351753
if not isinstance(base_url, str) or not base_url:
17361754
self.console.print("[bold red]Provider resolver returned an empty base URL.[/]")

tests/test_cli_provider_resolution.py

Lines changed: 24 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -162,6 +162,29 @@ def _runtime_resolve(**kwargs):
162162
assert shell.api_mode == "codex_responses"
163163

164164

165+
def test_runtime_resolution_reports_missing_main_provider_clearly(monkeypatch, capsys):
166+
cli = _import_cli()
167+
168+
def _runtime_resolve(**kwargs):
169+
return {
170+
"provider": "openrouter",
171+
"api_mode": "chat_completions",
172+
"base_url": "https://openrouter.ai/api/v1",
173+
"api_key": "",
174+
"source": "env/config",
175+
}
176+
177+
monkeypatch.setattr("gauss_cli.runtime_provider.resolve_runtime_provider", _runtime_resolve)
178+
monkeypatch.setattr("gauss_cli.runtime_provider.format_runtime_provider_error", lambda exc: str(exc))
179+
180+
shell = cli.GaussCLI(model="gpt-5", compact=True, max_turns=1)
181+
182+
assert shell._ensure_runtime_credentials() is False
183+
output = capsys.readouterr().out
184+
assert "No main interactive provider is configured yet." in output
185+
assert "gauss setup" in output
186+
187+
165188
def test_cli_prefers_config_provider_over_stale_env_override(monkeypatch):
166189
cli = _import_cli()
167190

@@ -374,4 +397,4 @@ def test_model_flow_custom_saves_verified_v1_base_url(monkeypatch, capsys):
374397
assert "Saving the working base URL instead" in output
375398
assert saved_env["OPENAI_BASE_URL"] == "http://localhost:8000/v1"
376399
assert saved_env["OPENAI_API_KEY"] == "local-key"
377-
assert saved_env["MODEL"] == "llm"
400+
assert saved_env["MODEL"] == "llm"

tests/test_morph_template.py

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
from pathlib import Path
2+
3+
import yaml
4+
5+
6+
TEMPLATE_PATH = Path(__file__).resolve().parents[1] / ".github" / "morph" / "opengauss-template.yaml"
7+
8+
9+
def _load_template() -> dict:
10+
return yaml.safe_load(TEMPLATE_PATH.read_text(encoding="utf-8"))
11+
12+
13+
def test_morph_template_restores_optional_provider_secret_staging():
14+
template = _load_template()
15+
steps = template["steps"]
16+
step_ids = [step["id"] for step in steps]
17+
by_id = {step["id"]: step for step in steps}
18+
19+
assert "stages optional provider keys" in template["description"]
20+
21+
expected_secret_steps = {
22+
"optional-openrouter": "OPENROUTER_API_KEY",
23+
"optional-openai": "OPENAI_API_KEY",
24+
"optional-anthropic": "ANTHROPIC_API_KEY",
25+
}
26+
for step_id, secret_name in expected_secret_steps.items():
27+
step = by_id[step_id]
28+
assert step["type"] == "exportSecret"
29+
assert step["name"] == secret_name
30+
assert step["optional"] is True
31+
assert f'{secret_name}=' in step["run"]
32+
33+
finalize = by_id["finalize-provider-selection"]
34+
assert finalize["type"] == "command"
35+
assert "gauss-configure-main-provider" in finalize["run"]
36+
assert step_ids.index("optional-openrouter") < step_ids.index("finalize-provider-selection")
37+
assert step_ids.index("optional-openai") < step_ids.index("finalize-provider-selection")
38+
assert step_ids.index("optional-anthropic") < step_ids.index("finalize-provider-selection")
39+
assert step_ids.index("finalize-provider-selection") < step_ids.index("start-guide-server")

0 commit comments

Comments
 (0)