fix(github-research): encode repository file paths in content requests (#7018)

Preserve filename delimiters and literal percent escapes before either HTTP transport builds a Contents API request. Add offline regression coverage for requests and the urllib fallback.
This commit is contained in:
MsNBBc3R 2026-10-18 07:31:03 +08:00 committed by GitHub
parent 7e22e42365
commit 63ccc6ebfb
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
3 changed files with 54 additions and 3 deletions

View File

@ -1437,6 +1437,8 @@ Custom skill history preserves Unicode line separators inside saved content and
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.
File reads accept raw repository-relative paths and preserve spaces, Unicode,
`#`, `?`, and literal percent signs with either HTTP client.
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`.

View File

@ -8,6 +8,7 @@ import os
import json
import sys
from typing import Any, Dict, List, Optional
from urllib.parse import quote
try:
import requests
@ -113,10 +114,10 @@ class GitHubAPI:
raise
def get_file_content(self, owner: str, repo: str, path: str) -> str:
"""Get content of a specific file."""
"""Get content of a file using its raw repository-relative path."""
try:
return self._get(
f"/repos/{owner}/{repo}/contents/{path}",
f"/repos/{owner}/{repo}/contents/{quote(path, safe='/')}",
accept="application/vnd.github.raw",
)
except Exception as e:

View File

@ -1,4 +1,4 @@
"""Exercise the GitHub research client's dependency-free HTTP fallback."""
"""Exercise the GitHub research client's HTTP transports without network IO."""
import http.client
import io
@ -37,6 +37,54 @@ def fallback_client(monkeypatch):
return module["GitHubAPI"](), captured
@pytest.fixture(params=["requests", "urllib"])
def file_content_client(request, monkeypatch):
if request.param == "urllib":
return request.getfixturevalue("fallback_client")
requests = pytest.importorskip("requests")
script = (
Path(__file__).resolve().parents[2]
/ "skills/public/github-deep-research/scripts/github_api.py"
)
module = runpy.run_path(str(script))
captured = {}
def fake_send(session, prepared_request, **kwargs):
captured["url"] = prepared_request.url
response = requests.Response()
response.status_code = 200
response._content = b'{"ok": true}'
return response
monkeypatch.setattr(requests.Session, "send", fake_send)
return module["GitHubAPI"](), captured
@pytest.mark.parametrize(
("path", "encoded_path"),
[
("docs/C# guide.md", "docs/C%23%20guide.md"),
("docs/query?draft.md", "docs/query%3Fdraft.md"),
("docs/literal%20name.md", "docs/literal%2520name.md"),
("文档/入门 guide.md", "%E6%96%87%E6%A1%A3/%E5%85%A5%E9%97%A8%20guide.md"),
("docs/file name.md", "docs/file%20name.md"),
("src/nested/README.md", "src/nested/README.md"),
],
)
def test_file_content_preserves_repository_path(
file_content_client, path, encoded_path
):
client, captured = file_content_client
assert client.get_file_content("owner", "repo", path) == '{"ok": true}'
url = urlsplit(captured["url"])
assert url.path == f"/repos/owner/repo/contents/{encoded_path}"
assert url.query == ""
assert url.fragment == ""
@pytest.mark.parametrize("query", ["bug fix", "C++ & retry#fragment", "模型请求"])
def test_fallback_search_preserves_query(fallback_client, query):
client, captured = fallback_client