Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
19 changes: 18 additions & 1 deletion agent/skill_commands.py
Original file line number Diff line number Diff line change
Expand Up @@ -104,7 +104,7 @@ def _build_skill_message(
f"[Skill setup note: {loaded_skill['gateway_setup_hint']}]",
]
)
elif loaded_skill.get("setup_needed") and loaded_skill.get("setup_note"):
elif loaded_skill.get("setup_note"):
parts.extend(
[
"",
Expand All @@ -127,6 +127,22 @@ def _build_skill_message(
rel = str(f.relative_to(skill_dir))
supporting.append(rel)

# If no explicit setup note made it through, synthesize a remote reminder when on remote backends
try:
import os as _os_for_msg
_backend_for_msg = str(_os_for_msg.getenv("TERMINAL_ENV", "local")).strip().lower() or "local"
_REMOTE_ENV_BACKENDS_FOR_MSG = {"docker", "singularity", "modal", "ssh", "daytona"}
except Exception:
_backend_for_msg = "local"
_REMOTE_ENV_BACKENDS_FOR_MSG = set()
if (not any("[Skill setup note:" in p for p in parts)
and _backend_for_msg in _REMOTE_ENV_BACKENDS_FOR_MSG
and (loaded_skill.get("required_environment_variables") or [])):
parts.extend([
"",
f"[Skill setup note: {_backend_for_msg.upper()}-backed skills need these requirements available inside the remote environment as well.]",
])

if supporting and skill_dir:
try:
skill_view_target = str(skill_dir.relative_to(SKILLS_DIR))
Expand Down Expand Up @@ -292,6 +308,7 @@ def build_preloaded_skills_prompt(
activation_note,
)
)

loaded_names.append(skill_name)

return "\n\n".join(prompt_parts), loaded_names, missing
1 change: 1 addition & 0 deletions gateway/platforms/api_server.py
Original file line number Diff line number Diff line change
Expand Up @@ -165,6 +165,7 @@ def __len__(self) -> int:
# ---------------------------------------------------------------------------

_CORS_HEADERS = {
"Access-Control-Expose-Headers": "Location, X-Request-Id, Idempotency-Key",
"Access-Control-Allow-Methods": "GET, POST, DELETE, OPTIONS",
"Access-Control-Allow-Headers": "Authorization, Content-Type, Idempotency-Key",
}
Expand Down
12 changes: 12 additions & 0 deletions tests/gateway/test_api_server.py
Original file line number Diff line number Diff line change
Expand Up @@ -1501,3 +1501,15 @@ async def test_conversation_store_false_no_mapping(self, adapter):
assert resp.status == 200
# Conversation mapping should NOT be set since store=false
assert adapter._response_store.get_conversation("ephemeral-chat") is None

@pytest.mark.asyncio
async def test_cors_expose_headers_present(self):
adapter = _make_adapter(cors_origins=["http://localhost:3000"])
app = _create_app(adapter)
async with TestClient(TestServer(app)) as cli:
resp = await cli.get("/v1/models", headers={"Origin": "http://localhost:3000"})
assert resp.status == 200
# Exposed headers should include our list; present on simple responses
assert "Location" in resp.headers.get("Access-Control-Expose-Headers", "")
assert "X-Request-Id" in resp.headers.get("Access-Control-Expose-Headers", "")
assert "Idempotency-Key" in resp.headers.get("Access-Control-Expose-Headers", "")
Loading