mirror of
https://github.com/bytedance/deer-flow.git
synced 2026-10-26 13:16:24 +00:00
fix(skills): encode GitHub API fallback query parameters (#6661)
Co-authored-by: ranjianghong526-hash <228174768+ranjianghong526-hash@users.noreply.github.com>
This commit is contained in:
parent
128dafd0ce
commit
04a45db836
@ -225,6 +225,8 @@ These apply repo-wide; module guides own the module-specific detail.
|
||||
- **Test-driven development** — features and bug fixes ship with tests. Backend tests live
|
||||
in `backend/tests/` (TDD is mandatory there; see [backend/AGENTS.md](backend/AGENTS.md));
|
||||
frontend tests live in `frontend/tests/`.
|
||||
Public skill script regressions live in `tests/skills/`; exercise dependency fallbacks
|
||||
with the optional import unavailable and mock HTTP transport to keep tests offline.
|
||||
- **Format before pushing** — run `make format` (backend) / `pnpm check` (frontend). Backend
|
||||
CI enforces `ruff format --check`, so formatting must be clean before a push.
|
||||
- **Skill text encoding** — treat `SKILL.md` and other textual skill resources as UTF-8;
|
||||
|
||||
@ -1299,6 +1299,10 @@ Skill Markdown and bundled text resources use UTF-8. Skill-creator CLI and revie
|
||||
|
||||
Custom skill history preserves Unicode line separators inside saved content and metadata, keeping those revisions readable for history inspection and rollback. Malformed JSON history records are still rejected.
|
||||
|
||||
The bundled `github-deep-research` API client also works without `requests`, using
|
||||
Python's standard-library HTTP client. Its fallback preserves spaces, Unicode, and
|
||||
reserved characters in search queries and label filters.
|
||||
|
||||
Users can explicitly activate an enabled skill for a single turn by starting the request with `/skill-name`, for example `/data-analysis analyze uploads/foo.csv`. DeerFlow loads that skill's `SKILL.md` as hidden current-turn context while leaving the base prompt limited to skill metadata. Slash activation respects disabled skills, custom-agent skill whitelists, and existing channel commands such as `/new` and `/help`.
|
||||
|
||||
After an answer loads skills, its toolbar includes **Skills used**. Hover over or click the icon to see the skills and their sources; hovering a skill name underlines it. Select a skill to inspect its `SKILL.md` in the resizable side panel (a drawer on mobile). The view uses snapshots captured during successful configured read-tool loads or explicit slash activation, so later edits or removal of a skill do not rewrite its history. Repeated loads appear once per run, in first-load order. Range reads and bounded snapshots are labeled as partial; older conversations without captured evidence do not show this menu. Copy returns the captured Markdown, including YAML frontmatter. Package-relative links and images remain readable references rather than navigating away from the conversation. Skill loads performed through other tools, such as shell commands, are not inferred from answer text.
|
||||
|
||||
@ -14,6 +14,7 @@ try:
|
||||
except ImportError:
|
||||
# Fallback to urllib if requests not available
|
||||
import urllib.error
|
||||
import urllib.parse
|
||||
import urllib.request
|
||||
|
||||
class RequestsFallback:
|
||||
@ -35,7 +36,7 @@ except ImportError:
|
||||
@staticmethod
|
||||
def get(url: str, headers: dict = None, params: dict = None, timeout: int = 30):
|
||||
if params:
|
||||
query = "&".join(f"{k}={v}" for k, v in params.items())
|
||||
query = urllib.parse.urlencode(params)
|
||||
url = f"{url}?{query}"
|
||||
|
||||
req = urllib.request.Request(url, headers=headers or {})
|
||||
|
||||
84
tests/skills/test_github_deep_research.py
Normal file
84
tests/skills/test_github_deep_research.py
Normal file
@ -0,0 +1,84 @@
|
||||
"""Exercise the GitHub research client's dependency-free HTTP fallback."""
|
||||
|
||||
import http.client
|
||||
import io
|
||||
import runpy
|
||||
import sys
|
||||
import urllib.request
|
||||
from pathlib import Path
|
||||
from urllib.parse import parse_qs, urlsplit
|
||||
|
||||
import pytest
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def fallback_client(monkeypatch):
|
||||
monkeypatch.setitem(sys.modules, "requests", None)
|
||||
script = (
|
||||
Path(__file__).resolve().parents[2]
|
||||
/ "skills/public/github-deep-research/scripts/github_api.py"
|
||||
)
|
||||
module = runpy.run_path(str(script))
|
||||
captured = {}
|
||||
|
||||
def fake_urlopen(request, timeout):
|
||||
# Validate the request target as the real HTTP transport does, without IO.
|
||||
connection = http.client.HTTPSConnection("api.github.com")
|
||||
try:
|
||||
connection.putrequest("GET", request.selector)
|
||||
finally:
|
||||
connection.close()
|
||||
captured.update(url=request.full_url, timeout=timeout, headers=request.headers)
|
||||
response = io.BytesIO(b'{"ok": true}')
|
||||
response.status = 200
|
||||
return response
|
||||
|
||||
monkeypatch.setattr(urllib.request, "urlopen", fake_urlopen)
|
||||
return module["GitHubAPI"](), captured
|
||||
|
||||
|
||||
@pytest.mark.parametrize("query", ["bug fix", "C++ & retry#fragment", "模型请求"])
|
||||
def test_fallback_search_preserves_query(fallback_client, query):
|
||||
client, captured = fallback_client
|
||||
|
||||
assert client.search_issues("owner", "repo", query) == {"ok": True}
|
||||
|
||||
url = urlsplit(captured["url"])
|
||||
assert url.path == "/search/issues"
|
||||
assert url.fragment == ""
|
||||
assert parse_qs(url.query) == {
|
||||
"q": [f"repo:owner/repo {query}"],
|
||||
"per_page": ["30"],
|
||||
}
|
||||
|
||||
|
||||
def test_fallback_issues_preserves_filters(fallback_client):
|
||||
client, captured = fallback_client
|
||||
|
||||
assert client.get_issues(
|
||||
"owner", "repo", state="open", limit=10, labels="bug,help wanted"
|
||||
) == {"ok": True}
|
||||
|
||||
assert parse_qs(urlsplit(captured["url"]).query) == {
|
||||
"state": ["open"],
|
||||
"per_page": ["10"],
|
||||
"labels": ["bug,help wanted"],
|
||||
}
|
||||
|
||||
|
||||
def test_fallback_ordinary_parameters(fallback_client):
|
||||
client, captured = fallback_client
|
||||
|
||||
assert client.get_contributors("owner", "repo", limit=10) == {"ok": True}
|
||||
|
||||
assert parse_qs(urlsplit(captured["url"]).query) == {"per_page": ["10"]}
|
||||
assert captured["timeout"] == 30
|
||||
assert captured["headers"]["User-agent"] == "Deep-Research-Bot/1.0"
|
||||
|
||||
|
||||
def test_fallback_without_parameters(fallback_client):
|
||||
client, captured = fallback_client
|
||||
|
||||
assert client.get_repo_info("owner", "repo") == {"ok": True}
|
||||
|
||||
assert captured["url"] == "https://api.github.com/repos/owner/repo"
|
||||
Loading…
x
Reference in New Issue
Block a user