Spaces:
Runtime error
Runtime error
deepagent-bot commited on
Commit ·
ff25935
1
Parent(s): 0d7206c
ci: sync dashboard from 2bc80cd
Browse filesSource: github.com/CouLiBaLy-B/gh-deepagents@2bc80cd9efa80019caf02d83b448842dfd4f78b5
src/gh_deepagent/dashboard/pages/5_🚀_Trigger.py
CHANGED
|
@@ -1,8 +1,9 @@
|
|
| 1 |
"""Trigger page — kick off a job locally (CLI shortcut).
|
| 2 |
|
| 3 |
-
|
| 4 |
-
|
| 5 |
-
|
|
|
|
| 6 |
"""
|
| 7 |
from __future__ import annotations
|
| 8 |
|
|
@@ -33,6 +34,39 @@ if not user.get("is_admin"):
|
|
| 33 |
)
|
| 34 |
st.stop()
|
| 35 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 36 |
st.warning(
|
| 37 |
"⚠️ This page runs the agent inside the Streamlit process. It will block "
|
| 38 |
"the UI for the duration of the run. For long jobs, use the queue."
|
|
@@ -40,15 +74,21 @@ st.warning(
|
|
| 40 |
|
| 41 |
tab_fix, tab_evolve, tab_review = st.tabs(["Fix issue", "Evolve repo", "Review PR"])
|
| 42 |
|
|
|
|
|
|
|
|
|
|
| 43 |
with tab_fix:
|
| 44 |
-
issue_url = st.text_input(
|
| 45 |
-
|
|
|
|
|
|
|
|
|
|
| 46 |
dry = st.checkbox("Dry-run (no PR)", key="dry-fix")
|
| 47 |
if st.button("Run fix_issue", type="primary", disabled=not issue_url):
|
| 48 |
with st.spinner("Agent running…"):
|
| 49 |
try:
|
| 50 |
from gh_deepagent.runner import fix_issue
|
| 51 |
-
res = fix_issue(issue_url, dry_run=dry)
|
| 52 |
if res.pr_url:
|
| 53 |
st.success(f"✅ PR opened: {res.pr_url}")
|
| 54 |
st.markdown(res.summary or "(no summary)")
|
|
@@ -59,9 +99,15 @@ with tab_fix:
|
|
| 59 |
st.error(f"Failed: {e}")
|
| 60 |
|
| 61 |
with tab_evolve:
|
| 62 |
-
repo = st.text_input(
|
| 63 |
-
|
| 64 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 65 |
height=120, key="evolve-instr")
|
| 66 |
dry2 = st.checkbox("Dry-run (no PR)", key="dry-evolve")
|
| 67 |
if st.button("Run evolve_code", type="primary",
|
|
@@ -80,8 +126,13 @@ with tab_evolve:
|
|
| 80 |
st.error(f"Failed: {e}")
|
| 81 |
|
| 82 |
with tab_review:
|
| 83 |
-
repo3 = st.text_input(
|
| 84 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 85 |
pr_n = st.number_input("PR number", min_value=1, step=1, key="review-pr")
|
| 86 |
if st.button("Run review_pr", type="primary",
|
| 87 |
disabled=not (repo3 and pr_n)):
|
|
|
|
| 1 |
"""Trigger page — kick off a job locally (CLI shortcut).
|
| 2 |
|
| 3 |
+
Runs the agent **in-process** via the runner, NOT through the queue. Useful
|
| 4 |
+
for one-off operator interventions. The LLM provider/model/token used here
|
| 5 |
+
comes from the env (or from the **⚙️ LLM Settings** page, which writes to
|
| 6 |
+
the same env vars for the current process).
|
| 7 |
"""
|
| 8 |
from __future__ import annotations
|
| 9 |
|
|
|
|
| 34 |
)
|
| 35 |
st.stop()
|
| 36 |
|
| 37 |
+
# --- Current LLM banner -----------------------------------------------
|
| 38 |
+
_spec = st.session_state.get("llm.spec") or os.getenv(
|
| 39 |
+
"DEEPAGENT_MODEL", "anthropic:claude-sonnet-4-5"
|
| 40 |
+
)
|
| 41 |
+
_provider = _spec.split(":", 1)[0] if ":" in _spec else _spec
|
| 42 |
+
_key_env_map = {
|
| 43 |
+
"anthropic": "ANTHROPIC_API_KEY",
|
| 44 |
+
"openai": "OPENAI_API_KEY",
|
| 45 |
+
"google_genai": "GOOGLE_API_KEY",
|
| 46 |
+
"groq": "GROQ_API_KEY",
|
| 47 |
+
}
|
| 48 |
+
_key_env = _key_env_map.get(_provider)
|
| 49 |
+
_has_key = (not _key_env) or bool(os.getenv(_key_env))
|
| 50 |
+
cols = st.columns([3, 1])
|
| 51 |
+
with cols[0]:
|
| 52 |
+
st.info(
|
| 53 |
+
f"**Current LLM:** `{_spec}` "
|
| 54 |
+
+ (f"· {_key_env} {'✅' if _has_key else '❌ missing'}" if _key_env else "")
|
| 55 |
+
+ " — change it on the **⚙️ LLM Settings** page."
|
| 56 |
+
)
|
| 57 |
+
with cols[1]:
|
| 58 |
+
st.page_link(
|
| 59 |
+
"pages/8_⚙️_LLM_Settings.py",
|
| 60 |
+
label="⚙️ Configure LLM",
|
| 61 |
+
icon="🔧",
|
| 62 |
+
)
|
| 63 |
+
|
| 64 |
+
if not _has_key:
|
| 65 |
+
st.error(
|
| 66 |
+
f"⛔ The chosen provider needs `{_key_env}` to be set. "
|
| 67 |
+
"Set it on the LLM Settings page or in the Space *Settings → Variables and secrets*."
|
| 68 |
+
)
|
| 69 |
+
|
| 70 |
st.warning(
|
| 71 |
"⚠️ This page runs the agent inside the Streamlit process. It will block "
|
| 72 |
"the UI for the duration of the run. For long jobs, use the queue."
|
|
|
|
| 74 |
|
| 75 |
tab_fix, tab_evolve, tab_review = st.tabs(["Fix issue", "Evolve repo", "Review PR"])
|
| 76 |
|
| 77 |
+
# Helper to give consistent hints
|
| 78 |
+
_REPO_HELP = "Accepts `owner/repo`, a full GitHub URL, or an SSH URL — all normalised automatically."
|
| 79 |
+
|
| 80 |
with tab_fix:
|
| 81 |
+
issue_url = st.text_input(
|
| 82 |
+
"Issue URL",
|
| 83 |
+
placeholder="https://github.com/org/repo/issues/42",
|
| 84 |
+
help="Must be a full GitHub issue URL.",
|
| 85 |
+
)
|
| 86 |
dry = st.checkbox("Dry-run (no PR)", key="dry-fix")
|
| 87 |
if st.button("Run fix_issue", type="primary", disabled=not issue_url):
|
| 88 |
with st.spinner("Agent running…"):
|
| 89 |
try:
|
| 90 |
from gh_deepagent.runner import fix_issue
|
| 91 |
+
res = fix_issue(issue_url.strip(), dry_run=dry)
|
| 92 |
if res.pr_url:
|
| 93 |
st.success(f"✅ PR opened: {res.pr_url}")
|
| 94 |
st.markdown(res.summary or "(no summary)")
|
|
|
|
| 99 |
st.error(f"Failed: {e}")
|
| 100 |
|
| 101 |
with tab_evolve:
|
| 102 |
+
repo = st.text_input(
|
| 103 |
+
"Repo",
|
| 104 |
+
value=os.getenv("DEEPAGENT_DEFAULT_REPO", ""),
|
| 105 |
+
key="evolve-repo",
|
| 106 |
+
help=_REPO_HELP,
|
| 107 |
+
placeholder="owner/repo or https://github.com/owner/repo",
|
| 108 |
+
)
|
| 109 |
+
instruction = st.text_area("Instruction",
|
| 110 |
+
placeholder="What should the agent change?",
|
| 111 |
height=120, key="evolve-instr")
|
| 112 |
dry2 = st.checkbox("Dry-run (no PR)", key="dry-evolve")
|
| 113 |
if st.button("Run evolve_code", type="primary",
|
|
|
|
| 126 |
st.error(f"Failed: {e}")
|
| 127 |
|
| 128 |
with tab_review:
|
| 129 |
+
repo3 = st.text_input(
|
| 130 |
+
"Repo",
|
| 131 |
+
value=os.getenv("DEEPAGENT_DEFAULT_REPO", ""),
|
| 132 |
+
key="review-repo",
|
| 133 |
+
help=_REPO_HELP,
|
| 134 |
+
placeholder="owner/repo or https://github.com/owner/repo",
|
| 135 |
+
)
|
| 136 |
pr_n = st.number_input("PR number", min_value=1, step=1, key="review-pr")
|
| 137 |
if st.button("Run review_pr", type="primary",
|
| 138 |
disabled=not (repo3 and pr_n)):
|
src/gh_deepagent/dashboard/pages/8_⚙️_LLM_Settings.py
ADDED
|
@@ -0,0 +1,255 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""LLM provider / model / API-key configuration page.
|
| 2 |
+
|
| 3 |
+
For the **all-in-one demo Space**, this page lets any admin reconfigure the LLM
|
| 4 |
+
**without restarting the container**. The settings are written to
|
| 5 |
+
``st.session_state`` and pushed into ``os.environ`` so that the in-process
|
| 6 |
+
``runner.fix_issue / evolve_code / review_pr`` calls (page 🚀 Trigger) pick
|
| 7 |
+
them up immediately.
|
| 8 |
+
|
| 9 |
+
Limitations:
|
| 10 |
+
- Workers running in *other processes* keep the env vars they were started
|
| 11 |
+
with. For a multi-process deployment you still need to set DEEPAGENT_MODEL
|
| 12 |
+
+ the right API key at container-start time.
|
| 13 |
+
- For the standalone (no-backend) dashboard this page is informative — you
|
| 14 |
+
can configure values for a future backend you'll deploy yourself.
|
| 15 |
+
"""
|
| 16 |
+
from __future__ import annotations
|
| 17 |
+
|
| 18 |
+
import os
|
| 19 |
+
|
| 20 |
+
import streamlit as st
|
| 21 |
+
|
| 22 |
+
from gh_deepagent.dashboard.auth_ui import render_user_badge, require_login
|
| 23 |
+
|
| 24 |
+
|
| 25 |
+
st.set_page_config(page_title="LLM Settings · gh-deepagent",
|
| 26 |
+
page_icon="⚙️", layout="wide")
|
| 27 |
+
st.title("⚙️ LLM provider, model & token")
|
| 28 |
+
st.caption(
|
| 29 |
+
"Configure which LLM the agent should use. Changes apply **in this Space, "
|
| 30 |
+
"right now**, for jobs triggered via the dashboard. They do NOT propagate "
|
| 31 |
+
"to other Spaces or to workers in other processes."
|
| 32 |
+
)
|
| 33 |
+
|
| 34 |
+
_api, user = require_login()
|
| 35 |
+
render_user_badge()
|
| 36 |
+
if not user.get("is_admin"):
|
| 37 |
+
st.error("LLM settings are restricted to admins.")
|
| 38 |
+
st.stop()
|
| 39 |
+
|
| 40 |
+
|
| 41 |
+
# ----------------------------------------------------- known providers
|
| 42 |
+
PROVIDERS = {
|
| 43 |
+
"anthropic": {
|
| 44 |
+
"label": "Anthropic Claude",
|
| 45 |
+
"env_key": "ANTHROPIC_API_KEY",
|
| 46 |
+
"models": [
|
| 47 |
+
"claude-sonnet-4-5",
|
| 48 |
+
"claude-opus-4",
|
| 49 |
+
"claude-haiku-4",
|
| 50 |
+
"claude-sonnet-4-20250514",
|
| 51 |
+
],
|
| 52 |
+
"spec_prefix": "anthropic:",
|
| 53 |
+
"doc": "https://docs.anthropic.com/en/api/getting-started",
|
| 54 |
+
},
|
| 55 |
+
"openai": {
|
| 56 |
+
"label": "OpenAI",
|
| 57 |
+
"env_key": "OPENAI_API_KEY",
|
| 58 |
+
"models": [
|
| 59 |
+
"gpt-4o-mini",
|
| 60 |
+
"gpt-4o",
|
| 61 |
+
"gpt-4.1",
|
| 62 |
+
"gpt-4.1-mini",
|
| 63 |
+
"gpt-5",
|
| 64 |
+
"o4-mini",
|
| 65 |
+
],
|
| 66 |
+
"spec_prefix": "openai:",
|
| 67 |
+
"doc": "https://platform.openai.com/api-keys",
|
| 68 |
+
},
|
| 69 |
+
"google_genai": {
|
| 70 |
+
"label": "Google Gemini",
|
| 71 |
+
"env_key": "GOOGLE_API_KEY",
|
| 72 |
+
"models": [
|
| 73 |
+
"gemini-2.5-flash",
|
| 74 |
+
"gemini-2.5-pro",
|
| 75 |
+
],
|
| 76 |
+
"spec_prefix": "google_genai:",
|
| 77 |
+
"doc": "https://aistudio.google.com/apikey",
|
| 78 |
+
},
|
| 79 |
+
"groq": {
|
| 80 |
+
"label": "Groq (Llama, Mixtral, …)",
|
| 81 |
+
"env_key": "GROQ_API_KEY",
|
| 82 |
+
"models": [
|
| 83 |
+
"llama-3.3-70b-versatile",
|
| 84 |
+
"llama-3.1-70b-versatile",
|
| 85 |
+
"mixtral-8x7b-32768",
|
| 86 |
+
],
|
| 87 |
+
"spec_prefix": "groq:",
|
| 88 |
+
"doc": "https://console.groq.com/keys",
|
| 89 |
+
},
|
| 90 |
+
"ollama": {
|
| 91 |
+
"label": "Ollama (local, no API key)",
|
| 92 |
+
"env_key": "", # no key
|
| 93 |
+
"models": [
|
| 94 |
+
"qwen2.5-coder:14b",
|
| 95 |
+
"deepseek-coder-v2:16b",
|
| 96 |
+
"llama3.1:8b",
|
| 97 |
+
],
|
| 98 |
+
"spec_prefix": "ollama:",
|
| 99 |
+
"doc": "https://ollama.com/library",
|
| 100 |
+
},
|
| 101 |
+
}
|
| 102 |
+
|
| 103 |
+
|
| 104 |
+
# ----------------------------------------------------- current state
|
| 105 |
+
def _current_spec() -> str:
|
| 106 |
+
return st.session_state.get("llm.spec") or os.getenv(
|
| 107 |
+
"DEEPAGENT_MODEL", "anthropic:claude-sonnet-4-5"
|
| 108 |
+
)
|
| 109 |
+
|
| 110 |
+
|
| 111 |
+
def _current_provider() -> str:
|
| 112 |
+
spec = _current_spec()
|
| 113 |
+
for pid, p in PROVIDERS.items():
|
| 114 |
+
if spec.startswith(p["spec_prefix"]):
|
| 115 |
+
return pid
|
| 116 |
+
return "anthropic"
|
| 117 |
+
|
| 118 |
+
|
| 119 |
+
# ----------------------------------------------------- UI
|
| 120 |
+
st.subheader("Current configuration")
|
| 121 |
+
|
| 122 |
+
cur_provider = _current_provider()
|
| 123 |
+
cur_spec = _current_spec()
|
| 124 |
+
cur_model = cur_spec.split(":", 1)[1] if ":" in cur_spec else cur_spec
|
| 125 |
+
|
| 126 |
+
cc = st.columns(3)
|
| 127 |
+
cc[0].metric("Provider", PROVIDERS[cur_provider]["label"])
|
| 128 |
+
cc[1].metric("Model", cur_model)
|
| 129 |
+
key_env = PROVIDERS[cur_provider]["env_key"]
|
| 130 |
+
if key_env:
|
| 131 |
+
key_set = bool(os.getenv(key_env))
|
| 132 |
+
cc[2].metric(
|
| 133 |
+
f"{key_env}",
|
| 134 |
+
"✅ set" if key_set else "❌ missing",
|
| 135 |
+
delta=None,
|
| 136 |
+
)
|
| 137 |
+
else:
|
| 138 |
+
cc[2].metric("API key", "— (none required)")
|
| 139 |
+
|
| 140 |
+
st.divider()
|
| 141 |
+
|
| 142 |
+
# ----------------------------------------------------- form
|
| 143 |
+
st.subheader("Change configuration")
|
| 144 |
+
|
| 145 |
+
with st.form("llm-settings-form"):
|
| 146 |
+
provider_id = st.selectbox(
|
| 147 |
+
"Provider",
|
| 148 |
+
list(PROVIDERS.keys()),
|
| 149 |
+
index=list(PROVIDERS.keys()).index(cur_provider),
|
| 150 |
+
format_func=lambda k: PROVIDERS[k]["label"],
|
| 151 |
+
)
|
| 152 |
+
p = PROVIDERS[provider_id]
|
| 153 |
+
|
| 154 |
+
# Model selector: known models + free-text override
|
| 155 |
+
model_options = p["models"] + ["(custom — type below)"]
|
| 156 |
+
default_idx = (
|
| 157 |
+
model_options.index(cur_model) if cur_model in model_options else 0
|
| 158 |
+
)
|
| 159 |
+
model_choice = st.selectbox("Model", model_options, index=default_idx)
|
| 160 |
+
custom_model = st.text_input(
|
| 161 |
+
"Custom model name",
|
| 162 |
+
value=cur_model if cur_model not in p["models"] else "",
|
| 163 |
+
disabled=model_choice != "(custom — type below)",
|
| 164 |
+
placeholder="e.g. my-org/my-finetune",
|
| 165 |
+
)
|
| 166 |
+
chosen_model = custom_model if model_choice == "(custom — type below)" else model_choice
|
| 167 |
+
|
| 168 |
+
api_key = ""
|
| 169 |
+
if p["env_key"]:
|
| 170 |
+
st.markdown(
|
| 171 |
+
f"**API key** → stored as env var `{p['env_key']}` "
|
| 172 |
+
f"([get one]({p['doc']}))"
|
| 173 |
+
)
|
| 174 |
+
api_key = st.text_input(
|
| 175 |
+
f"{p['env_key']}",
|
| 176 |
+
type="password",
|
| 177 |
+
value="", # never display the existing value
|
| 178 |
+
placeholder="Leave blank to keep the current one",
|
| 179 |
+
)
|
| 180 |
+
else:
|
| 181 |
+
st.caption(f"No API key needed for {p['label']}. "
|
| 182 |
+
f"Make sure an Ollama server is reachable.")
|
| 183 |
+
|
| 184 |
+
base_url = ""
|
| 185 |
+
if provider_id == "ollama":
|
| 186 |
+
base_url = st.text_input(
|
| 187 |
+
"OLLAMA_BASE_URL",
|
| 188 |
+
value=os.getenv("OLLAMA_BASE_URL", "http://localhost:11434"),
|
| 189 |
+
)
|
| 190 |
+
|
| 191 |
+
submitted = st.form_submit_button("Apply", type="primary")
|
| 192 |
+
|
| 193 |
+
if submitted:
|
| 194 |
+
if not chosen_model:
|
| 195 |
+
st.error("Pick or type a model name.")
|
| 196 |
+
st.stop()
|
| 197 |
+
|
| 198 |
+
spec = f"{p['spec_prefix']}{chosen_model}"
|
| 199 |
+
os.environ["DEEPAGENT_MODEL"] = spec
|
| 200 |
+
st.session_state["llm.spec"] = spec
|
| 201 |
+
|
| 202 |
+
if api_key and p["env_key"]:
|
| 203 |
+
os.environ[p["env_key"]] = api_key
|
| 204 |
+
st.session_state[f"llm.key.{p['env_key']}"] = "set" # marker only
|
| 205 |
+
if base_url and provider_id == "ollama":
|
| 206 |
+
os.environ["OLLAMA_BASE_URL"] = base_url
|
| 207 |
+
|
| 208 |
+
# The Settings cache is frozen — invalidate it so the next get_settings()
|
| 209 |
+
# picks up the new DEEPAGENT_MODEL.
|
| 210 |
+
try:
|
| 211 |
+
from gh_deepagent.config import get_settings
|
| 212 |
+
get_settings.cache_clear()
|
| 213 |
+
except Exception:
|
| 214 |
+
pass
|
| 215 |
+
|
| 216 |
+
st.success(f"✅ Applied: `{spec}`"
|
| 217 |
+
+ (f" with **{p['env_key']}**" if api_key else ""))
|
| 218 |
+
st.info(
|
| 219 |
+
"These settings apply to **jobs you launch from the dashboard right now**. "
|
| 220 |
+
"They won't reach other workers / processes. For a permanent change, set "
|
| 221 |
+
"the corresponding env vars in the Space *Settings → Variables and secrets*."
|
| 222 |
+
)
|
| 223 |
+
|
| 224 |
+
st.divider()
|
| 225 |
+
|
| 226 |
+
# ----------------------------------------------------- diagnostics
|
| 227 |
+
with st.expander("🔎 What's in the environment right now?"):
|
| 228 |
+
rows = []
|
| 229 |
+
for pid, info in PROVIDERS.items():
|
| 230 |
+
env = info["env_key"]
|
| 231 |
+
if not env:
|
| 232 |
+
continue
|
| 233 |
+
rows.append({
|
| 234 |
+
"Provider": info["label"],
|
| 235 |
+
"Env var": env,
|
| 236 |
+
"Set": "✅" if os.getenv(env) else "—",
|
| 237 |
+
})
|
| 238 |
+
rows.append({
|
| 239 |
+
"Provider": "—",
|
| 240 |
+
"Env var": "DEEPAGENT_MODEL",
|
| 241 |
+
"Set": os.getenv("DEEPAGENT_MODEL", "(default)"),
|
| 242 |
+
})
|
| 243 |
+
rows.append({
|
| 244 |
+
"Provider": "—",
|
| 245 |
+
"Env var": "OLLAMA_BASE_URL",
|
| 246 |
+
"Set": os.getenv("OLLAMA_BASE_URL", "(default)"),
|
| 247 |
+
})
|
| 248 |
+
import pandas as pd
|
| 249 |
+
st.dataframe(pd.DataFrame(rows), use_container_width=True, hide_index=True)
|
| 250 |
+
|
| 251 |
+
st.caption(
|
| 252 |
+
"💡 For production: configure these as Space *Variables* (model) and "
|
| 253 |
+
"*Secrets* (API key). They'll then apply to every container restart, "
|
| 254 |
+
"not just your current session."
|
| 255 |
+
)
|
src/gh_deepagent/github_client.py
CHANGED
|
@@ -153,3 +153,49 @@ def _parse_author(s: str) -> tuple[str, str]:
|
|
| 153 |
if not m:
|
| 154 |
return s.strip(), "bot@deepagent"
|
| 155 |
return m.group(1).strip(), m.group(2).strip()
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 153 |
if not m:
|
| 154 |
return s.strip(), "bot@deepagent"
|
| 155 |
return m.group(1).strip(), m.group(2).strip()
|
| 156 |
+
|
| 157 |
+
# ---------------------------------------------------------------- normaliser
|
| 158 |
+
|
| 159 |
+
# Accepts: owner/repo
|
| 160 |
+
# https://github.com/owner/repo
|
| 161 |
+
# https://github.com/owner/repo.git
|
| 162 |
+
# git@github.com:owner/repo.git
|
| 163 |
+
# https://x-access-token:TOK@github.com/owner/repo[.git]
|
| 164 |
+
_REPO_RE = re.compile(
|
| 165 |
+
r"""^(?:
|
| 166 |
+
(?:https?://(?:[^@/]+@)?github\.com/)
|
| 167 |
+
|
|
| 168 |
+
(?:git@github\.com:)
|
| 169 |
+
)?
|
| 170 |
+
(?P<owner>[A-Za-z0-9][A-Za-z0-9-]*)
|
| 171 |
+
/
|
| 172 |
+
(?P<repo>[A-Za-z0-9._-]+?)
|
| 173 |
+
(?:\.git)?
|
| 174 |
+
/?$""",
|
| 175 |
+
re.VERBOSE,
|
| 176 |
+
)
|
| 177 |
+
|
| 178 |
+
|
| 179 |
+
def normalize_repo_full_name(value: str) -> str:
|
| 180 |
+
"""Normalise any reasonable repo reference to ``owner/repo``.
|
| 181 |
+
|
| 182 |
+
Raises :class:`ValueError` if the value clearly isn't a GitHub repo ref.
|
| 183 |
+
"""
|
| 184 |
+
if not value or not isinstance(value, str):
|
| 185 |
+
raise ValueError(f"empty repo reference: {value!r}")
|
| 186 |
+
s = value.strip()
|
| 187 |
+
# If it has multiple URL-like substrings (user pasted "https://...https://..."),
|
| 188 |
+
# keep only the trailing one.
|
| 189 |
+
if s.count("https://") > 1 or s.count("http://") > 1:
|
| 190 |
+
# Take the segment after the last protocol marker.
|
| 191 |
+
for marker in ("https://", "http://"):
|
| 192 |
+
if marker in s:
|
| 193 |
+
s = marker + s.rsplit(marker, 1)[1]
|
| 194 |
+
m = _REPO_RE.match(s)
|
| 195 |
+
if not m:
|
| 196 |
+
raise ValueError(
|
| 197 |
+
f"Not a valid GitHub repo reference: {value!r}. "
|
| 198 |
+
"Expected formats: 'owner/repo', a full https URL, or an ssh URL."
|
| 199 |
+
)
|
| 200 |
+
return f"{m['owner']}/{m['repo']}"
|
| 201 |
+
|
src/gh_deepagent/runner.py
CHANGED
|
@@ -17,7 +17,7 @@ from rich.console import Console
|
|
| 17 |
|
| 18 |
from .agent import build_agent
|
| 19 |
from .config import get_settings
|
| 20 |
-
from .github_client import GitHubOps, IssueRef
|
| 21 |
|
| 22 |
console = Console()
|
| 23 |
|
|
@@ -149,6 +149,7 @@ def evolve_code(
|
|
| 149 |
backend: Optional[str] = None,
|
| 150 |
) -> RunResult:
|
| 151 |
"""Apply a free-form evolution request to the repo."""
|
|
|
|
| 152 |
settings = get_settings()
|
| 153 |
settings.assert_ready()
|
| 154 |
|
|
@@ -191,6 +192,7 @@ def iterate_pr(
|
|
| 191 |
|
| 192 |
Triggered by `/deepagent <instruction>` on a PR comment, or via CLI.
|
| 193 |
"""
|
|
|
|
| 194 |
settings = get_settings()
|
| 195 |
settings.assert_ready()
|
| 196 |
gh = GitHubOps()
|
|
@@ -253,6 +255,7 @@ def iterate_pr(
|
|
| 253 |
def review_pr(repo_full_name: str, pr_number: int, backend: Optional[str] = None) -> RunResult:
|
| 254 |
"""Post an automated code-review comment on a PR."""
|
| 255 |
import urllib.request
|
|
|
|
| 256 |
settings = get_settings()
|
| 257 |
settings.assert_ready()
|
| 258 |
gh = GitHubOps()
|
|
|
|
| 17 |
|
| 18 |
from .agent import build_agent
|
| 19 |
from .config import get_settings
|
| 20 |
+
from .github_client import GitHubOps, IssueRef, normalize_repo_full_name
|
| 21 |
|
| 22 |
console = Console()
|
| 23 |
|
|
|
|
| 149 |
backend: Optional[str] = None,
|
| 150 |
) -> RunResult:
|
| 151 |
"""Apply a free-form evolution request to the repo."""
|
| 152 |
+
repo_full_name = normalize_repo_full_name(repo_full_name)
|
| 153 |
settings = get_settings()
|
| 154 |
settings.assert_ready()
|
| 155 |
|
|
|
|
| 192 |
|
| 193 |
Triggered by `/deepagent <instruction>` on a PR comment, or via CLI.
|
| 194 |
"""
|
| 195 |
+
repo_full_name = normalize_repo_full_name(repo_full_name)
|
| 196 |
settings = get_settings()
|
| 197 |
settings.assert_ready()
|
| 198 |
gh = GitHubOps()
|
|
|
|
| 255 |
def review_pr(repo_full_name: str, pr_number: int, backend: Optional[str] = None) -> RunResult:
|
| 256 |
"""Post an automated code-review comment on a PR."""
|
| 257 |
import urllib.request
|
| 258 |
+
repo_full_name = normalize_repo_full_name(repo_full_name)
|
| 259 |
settings = get_settings()
|
| 260 |
settings.assert_ready()
|
| 261 |
gh = GitHubOps()
|