django-auto-agent 0.1.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (80) hide show
  1. django_auto_agent-0.1.0/LICENSE +21 -0
  2. django_auto_agent-0.1.0/MANIFEST.in +6 -0
  3. django_auto_agent-0.1.0/PKG-INFO +156 -0
  4. django_auto_agent-0.1.0/README.md +110 -0
  5. django_auto_agent-0.1.0/ai_agent/Dockerfile +12 -0
  6. django_auto_agent-0.1.0/ai_agent/__init__.py +9 -0
  7. django_auto_agent-0.1.0/ai_agent/admin/__init__.py +9 -0
  8. django_auto_agent-0.1.0/ai_agent/admin/_helpers.py +68 -0
  9. django_auto_agent-0.1.0/ai_agent/admin/_query.py +1256 -0
  10. django_auto_agent-0.1.0/ai_agent/admin/memories.py +167 -0
  11. django_auto_agent-0.1.0/ai_agent/admin/messages.py +40 -0
  12. django_auto_agent-0.1.0/ai_agent/admin/prompts.py +154 -0
  13. django_auto_agent-0.1.0/ai_agent/admin/threads.py +129 -0
  14. django_auto_agent-0.1.0/ai_agent/apps.py +14 -0
  15. django_auto_agent-0.1.0/ai_agent/auth.py +130 -0
  16. django_auto_agent-0.1.0/ai_agent/compaction.py +102 -0
  17. django_auto_agent-0.1.0/ai_agent/conf.py +494 -0
  18. django_auto_agent-0.1.0/ai_agent/context.py +212 -0
  19. django_auto_agent-0.1.0/ai_agent/discovery.py +239 -0
  20. django_auto_agent-0.1.0/ai_agent/evals/__init__.py +1 -0
  21. django_auto_agent-0.1.0/ai_agent/evals/cases.py +97 -0
  22. django_auto_agent-0.1.0/ai_agent/evals/fixtures.py +61 -0
  23. django_auto_agent-0.1.0/ai_agent/evals/harness.py +253 -0
  24. django_auto_agent-0.1.0/ai_agent/evals/report.py +71 -0
  25. django_auto_agent-0.1.0/ai_agent/evals/runtime.py +44 -0
  26. django_auto_agent-0.1.0/ai_agent/evals/scoring.py +375 -0
  27. django_auto_agent-0.1.0/ai_agent/executor.py +140 -0
  28. django_auto_agent-0.1.0/ai_agent/expose.py +63 -0
  29. django_auto_agent-0.1.0/ai_agent/graph.py +797 -0
  30. django_auto_agent-0.1.0/ai_agent/http_auth.py +114 -0
  31. django_auto_agent-0.1.0/ai_agent/limits.py +128 -0
  32. django_auto_agent-0.1.0/ai_agent/management/__init__.py +1 -0
  33. django_auto_agent-0.1.0/ai_agent/management/commands/__init__.py +1 -0
  34. django_auto_agent-0.1.0/ai_agent/management/commands/eval_agent.py +75 -0
  35. django_auto_agent-0.1.0/ai_agent/management/commands/optimize_agent_prompts.py +83 -0
  36. django_auto_agent-0.1.0/ai_agent/management/commands/reconcile_agent_memory.py +64 -0
  37. django_auto_agent-0.1.0/ai_agent/management/commands/seed_memory_optimizer_fixture.py +260 -0
  38. django_auto_agent-0.1.0/ai_agent/memory/__init__.py +11 -0
  39. django_auto_agent-0.1.0/ai_agent/memory/curator.py +416 -0
  40. django_auto_agent-0.1.0/ai_agent/memory/managers.py +311 -0
  41. django_auto_agent-0.1.0/ai_agent/memory/middleware.py +434 -0
  42. django_auto_agent-0.1.0/ai_agent/memory/namespaces.py +114 -0
  43. django_auto_agent-0.1.0/ai_agent/memory/optimizer_fixture.py +319 -0
  44. django_auto_agent-0.1.0/ai_agent/memory/prompt_optimize.py +1122 -0
  45. django_auto_agent-0.1.0/ai_agent/memory/reconcile.py +573 -0
  46. django_auto_agent-0.1.0/ai_agent/memory/schemas.py +80 -0
  47. django_auto_agent-0.1.0/ai_agent/memory/spec.py +61 -0
  48. django_auto_agent-0.1.0/ai_agent/memory/store.py +107 -0
  49. django_auto_agent-0.1.0/ai_agent/memory/supervisor_schemas.py +67 -0
  50. django_auto_agent-0.1.0/ai_agent/memory/tools.py +589 -0
  51. django_auto_agent-0.1.0/ai_agent/memory/user_memories.py +139 -0
  52. django_auto_agent-0.1.0/ai_agent/messages.py +24 -0
  53. django_auto_agent-0.1.0/ai_agent/migrations/0001_initial.py +53 -0
  54. django_auto_agent-0.1.0/ai_agent/migrations/0002_agentmessage.py +62 -0
  55. django_auto_agent-0.1.0/ai_agent/migrations/0003_agentprompt.py +31 -0
  56. django_auto_agent-0.1.0/ai_agent/migrations/__init__.py +1 -0
  57. django_auto_agent-0.1.0/ai_agent/models.py +71 -0
  58. django_auto_agent-0.1.0/ai_agent/runtime.py +192 -0
  59. django_auto_agent-0.1.0/ai_agent/safety.py +179 -0
  60. django_auto_agent-0.1.0/ai_agent/schema.py +376 -0
  61. django_auto_agent-0.1.0/ai_agent/serializers.py +26 -0
  62. django_auto_agent-0.1.0/ai_agent/studio.py +61 -0
  63. django_auto_agent-0.1.0/ai_agent/templates/admin/ai_agent/confirm_delete.html +28 -0
  64. django_auto_agent-0.1.0/ai_agent/templates/admin/ai_agent/memory_list.html +159 -0
  65. django_auto_agent-0.1.0/ai_agent/templates/admin/ai_agent/memory_user.html +213 -0
  66. django_auto_agent-0.1.0/ai_agent/templates/admin/ai_agent/prompt_detail.html +89 -0
  67. django_auto_agent-0.1.0/ai_agent/templates/admin/ai_agent/prompt_list.html +188 -0
  68. django_auto_agent-0.1.0/ai_agent/templates/admin/ai_agent/thread_detail.html +108 -0
  69. django_auto_agent-0.1.0/ai_agent/templates/admin/ai_agent/thread_list.html +141 -0
  70. django_auto_agent-0.1.0/ai_agent/tools.py +130 -0
  71. django_auto_agent-0.1.0/ai_agent/transcript.py +177 -0
  72. django_auto_agent-0.1.0/ai_agent/urls.py +18 -0
  73. django_auto_agent-0.1.0/ai_agent/views.py +178 -0
  74. django_auto_agent-0.1.0/django_auto_agent.egg-info/PKG-INFO +156 -0
  75. django_auto_agent-0.1.0/django_auto_agent.egg-info/SOURCES.txt +78 -0
  76. django_auto_agent-0.1.0/django_auto_agent.egg-info/dependency_links.txt +1 -0
  77. django_auto_agent-0.1.0/django_auto_agent.egg-info/requires.txt +17 -0
  78. django_auto_agent-0.1.0/django_auto_agent.egg-info/top_level.txt +1 -0
  79. django_auto_agent-0.1.0/pyproject.toml +70 -0
  80. django_auto_agent-0.1.0/setup.cfg +4 -0
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Alireza fz
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1,6 @@
1
+ recursive-include ai_agent/templates *
2
+ recursive-include ai_agent/migrations *
3
+ include ai_agent/Dockerfile
4
+ prune ai_agent/tests
5
+ global-exclude *.pyc
6
+ recursive-exclude * __pycache__
@@ -0,0 +1,156 @@
1
+ Metadata-Version: 2.4
2
+ Name: django-auto-agent
3
+ Version: 0.1.0
4
+ Summary: Django reusable app: DRF-to-LangGraph agent bridge with long-term memory.
5
+ Author: Alireza fz
6
+ License-Expression: MIT
7
+ Project-URL: Homepage, https://github.com/alirezafz/django-auto-agent
8
+ Project-URL: Repository, https://github.com/alirezafz/django-auto-agent
9
+ Project-URL: Issues, https://github.com/alirezafz/django-auto-agent/issues
10
+ Keywords: django,djangorestframework,langgraph,agent,copilotkit
11
+ Classifier: Development Status :: 4 - Beta
12
+ Classifier: Environment :: Web Environment
13
+ Classifier: Framework :: Django
14
+ Classifier: Framework :: Django :: 4.2
15
+ Classifier: Framework :: Django :: 5.0
16
+ Classifier: Framework :: Django :: 5.1
17
+ Classifier: Framework :: Django :: 5.2
18
+ Classifier: Intended Audience :: Developers
19
+ Classifier: Operating System :: OS Independent
20
+ Classifier: Programming Language :: Python
21
+ Classifier: Programming Language :: Python :: 3
22
+ Classifier: Programming Language :: Python :: 3.11
23
+ Classifier: Programming Language :: Python :: 3.12
24
+ Classifier: Programming Language :: Python :: 3.13
25
+ Classifier: Topic :: Internet :: WWW/HTTP
26
+ Requires-Python: >=3.11
27
+ Description-Content-Type: text/markdown
28
+ License-File: LICENSE
29
+ Requires-Dist: Django>=4.2
30
+ Requires-Dist: djangorestframework
31
+ Requires-Dist: drf-spectacular
32
+ Requires-Dist: fastapi
33
+ Requires-Dist: langchain>=1.0.0
34
+ Requires-Dist: langchain-openai>=1.0.0
35
+ Requires-Dist: langgraph>=1.0.0
36
+ Requires-Dist: langgraph-checkpoint-postgres>=2.0.0
37
+ Requires-Dist: langgraph-sdk>=0.1.61
38
+ Requires-Dist: langmem>=0.0.29
39
+ Requires-Dist: psycopg[binary]>=3.1.0
40
+ Requires-Dist: psycopg-pool>=3.2.0
41
+ Requires-Dist: copilotkit>=0.1.96
42
+ Provides-Extra: redis
43
+ Requires-Dist: redis>=5.0.0; extra == "redis"
44
+ Requires-Dist: langgraph-checkpoint-redis; extra == "redis"
45
+ Dynamic: license-file
46
+
47
+ # django-auto-agent
48
+
49
+ Django reusable app that turns opted-in DRF endpoints into a LangGraph supervisor, with optional long-term memory, admin views, and evals.
50
+
51
+ Import name: `ai_agent`.
52
+
53
+ ## Install
54
+
55
+ ```bash
56
+ pip install django-auto-agent
57
+ ```
58
+
59
+ Redis-backed memory store (optional):
60
+
61
+ ```bash
62
+ pip install "django-auto-agent[redis]"
63
+ ```
64
+
65
+ From a clone of this repository:
66
+
67
+ ```bash
68
+ pip install -e .
69
+ pip install -e ".[redis]"
70
+ ```
71
+
72
+ ## Host wiring
73
+
74
+ ```python
75
+ INSTALLED_APPS = [
76
+ # ...
77
+ "ai_agent",
78
+ "catalog", # your domain app
79
+ ]
80
+
81
+ AI_AGENT = {
82
+ "SUPERVISOR_MODEL": "openai:gpt-4.1-mini",
83
+ "SUBAGENT_MODEL": "openai:gpt-4.1-nano",
84
+ "AUTHENTICATE_TOKEN": "myproject.auth.authenticate_access_token",
85
+ "PLATFORM_NAME": "Acme",
86
+ "PLATFORM_DOMAINS": ("catalog", "billing"),
87
+ "EVAL_MODULE": "catalog.evals",
88
+ }
89
+ ```
90
+
91
+ `AUTHENTICATE_TOKEN` is required. It must be a callable (or dotted path to one) that accepts a bearer token and returns a user object with `.pk`.
92
+
93
+ Optional evals — point `EVAL_MODULE` at a host module that defines:
94
+
95
+ - `seed_eval_world() -> EvalWorld` (users plus `prompt_vars` for case prompts)
96
+ - `snapshot_world(world, kind) -> dict` (optional; used by mutation cases)
97
+ - `CASES: list[EvalCase]` (or partitioned `ROUTING_CASES` / `TOOL_CASES` / `MUTATION_CASES` / `E2E_CASES`)
98
+
99
+ The library always includes safety routing cases (off-policy greeting, jailbreaks). Domain tool and mutation cases come from the host module. See `dummy/evals.py` in this repo.
100
+
101
+ `HTTP_TRUSTED_PROXY_COUNT` (default `0`) controls whether the CopilotKit HTTP throttle reads `X-Forwarded-For`. Leave it at `0` unless the app sits behind that many trusted reverse proxies; otherwise clients can rotate a spoofed header and bypass the limit.
102
+
103
+ Include the chat/memory HTTP API:
104
+
105
+ ```python
106
+ urlpatterns = [
107
+ path("api/agent/", include("ai_agent.urls")),
108
+ ]
109
+ ```
110
+
111
+ Opt a host app into the agent by setting flags on its `AppConfig`:
112
+
113
+ ```python
114
+ class CatalogConfig(AppConfig):
115
+ name = "catalog"
116
+ agent_expose = True
117
+ agent_description = "List and create catalog items for the authenticated user."
118
+ agent_exclude = ("internal_webhook",)
119
+ agent_memory_profile = "catalog.memory_schemas.CatalogProfile"
120
+ agent_memory_collections = (
121
+ "ai_agent.memory.schemas.SemanticFact",
122
+ "catalog.memory_schemas.CatalogAccount",
123
+ )
124
+ ```
125
+
126
+ Mark individual views even when the app is off:
127
+
128
+ ```python
129
+ from ai_agent.expose import agent_expose, agent_exclude
130
+
131
+ @agent_expose
132
+ def public_note(request):
133
+ ...
134
+ ```
135
+
136
+ ## LangGraph Agent Server
137
+
138
+ This package is not a host Docker image. Install `django-auto-agent` **and** your Django project into the Agent Server image, then point `langgraph.json` at the library entrypoints (see the example `langgraph.json` in this repo):
139
+
140
+ - graphs: `ai_agent.studio:graph`, `ai_agent.studio:memory`
141
+ - auth: `ai_agent.auth:auth`
142
+ - HTTP app: `ai_agent.studio:app`
143
+
144
+ Set `DJANGO_SETTINGS_MODULE` (or `AI_AGENT_STUDIO_DJANGO_SETTINGS`) to **your** settings module before starting Studio.
145
+
146
+ The example `langgraph.json` uses `cors.allow_origins: ["*"]`. Auth is Bearer-token based (not cookies), so CSRF exposure is limited; restrict origins in production.
147
+
148
+ ## Tests
149
+
150
+ From this repo:
151
+
152
+ ```bash
153
+ python manage.py test ai_agent
154
+ ```
155
+
156
+ The `dummy` and `notes` apps plus `tests/settings.py` are a sample host. They are not installed with the wheel.
@@ -0,0 +1,110 @@
1
+ # django-auto-agent
2
+
3
+ Django reusable app that turns opted-in DRF endpoints into a LangGraph supervisor, with optional long-term memory, admin views, and evals.
4
+
5
+ Import name: `ai_agent`.
6
+
7
+ ## Install
8
+
9
+ ```bash
10
+ pip install django-auto-agent
11
+ ```
12
+
13
+ Redis-backed memory store (optional):
14
+
15
+ ```bash
16
+ pip install "django-auto-agent[redis]"
17
+ ```
18
+
19
+ From a clone of this repository:
20
+
21
+ ```bash
22
+ pip install -e .
23
+ pip install -e ".[redis]"
24
+ ```
25
+
26
+ ## Host wiring
27
+
28
+ ```python
29
+ INSTALLED_APPS = [
30
+ # ...
31
+ "ai_agent",
32
+ "catalog", # your domain app
33
+ ]
34
+
35
+ AI_AGENT = {
36
+ "SUPERVISOR_MODEL": "openai:gpt-4.1-mini",
37
+ "SUBAGENT_MODEL": "openai:gpt-4.1-nano",
38
+ "AUTHENTICATE_TOKEN": "myproject.auth.authenticate_access_token",
39
+ "PLATFORM_NAME": "Acme",
40
+ "PLATFORM_DOMAINS": ("catalog", "billing"),
41
+ "EVAL_MODULE": "catalog.evals",
42
+ }
43
+ ```
44
+
45
+ `AUTHENTICATE_TOKEN` is required. It must be a callable (or dotted path to one) that accepts a bearer token and returns a user object with `.pk`.
46
+
47
+ Optional evals — point `EVAL_MODULE` at a host module that defines:
48
+
49
+ - `seed_eval_world() -> EvalWorld` (users plus `prompt_vars` for case prompts)
50
+ - `snapshot_world(world, kind) -> dict` (optional; used by mutation cases)
51
+ - `CASES: list[EvalCase]` (or partitioned `ROUTING_CASES` / `TOOL_CASES` / `MUTATION_CASES` / `E2E_CASES`)
52
+
53
+ The library always includes safety routing cases (off-policy greeting, jailbreaks). Domain tool and mutation cases come from the host module. See `dummy/evals.py` in this repo.
54
+
55
+ `HTTP_TRUSTED_PROXY_COUNT` (default `0`) controls whether the CopilotKit HTTP throttle reads `X-Forwarded-For`. Leave it at `0` unless the app sits behind that many trusted reverse proxies; otherwise clients can rotate a spoofed header and bypass the limit.
56
+
57
+ Include the chat/memory HTTP API:
58
+
59
+ ```python
60
+ urlpatterns = [
61
+ path("api/agent/", include("ai_agent.urls")),
62
+ ]
63
+ ```
64
+
65
+ Opt a host app into the agent by setting flags on its `AppConfig`:
66
+
67
+ ```python
68
+ class CatalogConfig(AppConfig):
69
+ name = "catalog"
70
+ agent_expose = True
71
+ agent_description = "List and create catalog items for the authenticated user."
72
+ agent_exclude = ("internal_webhook",)
73
+ agent_memory_profile = "catalog.memory_schemas.CatalogProfile"
74
+ agent_memory_collections = (
75
+ "ai_agent.memory.schemas.SemanticFact",
76
+ "catalog.memory_schemas.CatalogAccount",
77
+ )
78
+ ```
79
+
80
+ Mark individual views even when the app is off:
81
+
82
+ ```python
83
+ from ai_agent.expose import agent_expose, agent_exclude
84
+
85
+ @agent_expose
86
+ def public_note(request):
87
+ ...
88
+ ```
89
+
90
+ ## LangGraph Agent Server
91
+
92
+ This package is not a host Docker image. Install `django-auto-agent` **and** your Django project into the Agent Server image, then point `langgraph.json` at the library entrypoints (see the example `langgraph.json` in this repo):
93
+
94
+ - graphs: `ai_agent.studio:graph`, `ai_agent.studio:memory`
95
+ - auth: `ai_agent.auth:auth`
96
+ - HTTP app: `ai_agent.studio:app`
97
+
98
+ Set `DJANGO_SETTINGS_MODULE` (or `AI_AGENT_STUDIO_DJANGO_SETTINGS`) to **your** settings module before starting Studio.
99
+
100
+ The example `langgraph.json` uses `cors.allow_origins: ["*"]`. Auth is Bearer-token based (not cookies), so CSRF exposure is limited; restrict origins in production.
101
+
102
+ ## Tests
103
+
104
+ From this repo:
105
+
106
+ ```bash
107
+ python manage.py test ai_agent
108
+ ```
109
+
110
+ The `dummy` and `notes` apps plus `tests/settings.py` are a sample host. They are not installed with the wheel.
@@ -0,0 +1,12 @@
1
+ # Example only. django-auto-agent is a pip library, not a host image.
2
+ #
3
+ # Build the LangGraph Agent Server from YOUR Django project:
4
+ # 1. pip install django-auto-agent
5
+ # 2. COPY your Django project into the image
6
+ # 3. Set DJANGO_SETTINGS_MODULE to your settings
7
+ # 4. Point langgraph.json at ai_agent.studio and ai_agent.auth
8
+ #
9
+ # See README.md and the example langgraph.json in this repository.
10
+
11
+ FROM langchain/langgraph-api:3.11-bookworm
12
+ RUN echo "Install django-auto-agent and your Django host project; do not copy a product tree into this library."
@@ -0,0 +1,9 @@
1
+ """Opt-in DRF-to-LangGraph agent bridge.
2
+
3
+ Extractable surface: expose, discovery, schema, executor, tools, graph, context.
4
+ Product wiring (chat view, Django settings) stays in this app.
5
+ """
6
+
7
+ from ai_agent.expose import agent_exclude, agent_expose
8
+
9
+ __all__ = ["agent_expose", "agent_exclude"]
@@ -0,0 +1,9 @@
1
+ """ai_agent admin package.
2
+
3
+ Model -> file map:
4
+ AgentThread -> threads.py
5
+ AgentMemory -> memories.py
6
+ AgentPrompt -> prompts.py
7
+ AgentMessage -> messages.py
8
+ """
9
+ from . import memories, messages, prompts, threads # noqa: F401
@@ -0,0 +1,68 @@
1
+ """Shared presentation helpers for AI agent admin views."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from django.core.paginator import EmptyPage, Paginator
6
+ from django.urls import NoReverseMatch, reverse
7
+
8
+
9
+ def user_display_name(user) -> str:
10
+ """Prefer first/last name, then ``get_username()``."""
11
+ if user is None:
12
+ return ""
13
+ name = " ".join(
14
+ part
15
+ for part in (getattr(user, "first_name", None), getattr(user, "last_name", None))
16
+ if part
17
+ )
18
+ if name:
19
+ return name
20
+ getter = getattr(user, "get_username", None)
21
+ if callable(getter):
22
+ return str(getter() or "").strip()
23
+ return ""
24
+
25
+
26
+ def user_change_url(user) -> str:
27
+ """Django admin change URL for the configured user model."""
28
+ if user is None:
29
+ return ""
30
+ opts = user._meta
31
+ try:
32
+ return reverse(
33
+ f"admin:{opts.app_label}_{opts.model_name}_change",
34
+ args=[user.pk],
35
+ )
36
+ except NoReverseMatch:
37
+ return ""
38
+
39
+
40
+ def paginate(request, items, total: int, *, per_page: int = 25):
41
+ """Build a Django page from an already-sliced list plus a known total."""
42
+ paginator = Paginator(range(total), per_page)
43
+ page_number = request.GET.get("p") or 1
44
+ try:
45
+ page = paginator.page(page_number)
46
+ except EmptyPage:
47
+ page = paginator.page(paginator.num_pages or 1)
48
+ page.object_list = items
49
+ return page, paginator
50
+
51
+
52
+ def page_offset(request, *, per_page: int = 25) -> int:
53
+ try:
54
+ page_number = int(request.GET.get("p") or 1)
55
+ except (TypeError, ValueError):
56
+ page_number = 1
57
+ if page_number < 1:
58
+ page_number = 1
59
+ return (page_number - 1) * per_page
60
+
61
+
62
+ def safe_next(raw: str, fallback: str) -> str:
63
+ """Allow only a relative query string on the same admin path."""
64
+ value = (raw or "").strip()
65
+ path, _, _ = fallback.partition("?")
66
+ if value.startswith("?") and "://" not in value and "\n" not in value:
67
+ return path + value
68
+ return fallback