Skip to content
Closed
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
1 change: 1 addition & 0 deletions deeptutor/services/provider_registry.py
Original file line number Diff line number Diff line change
Expand Up @@ -337,6 +337,7 @@ def canonical_provider_name(name: str | None) -> str | None:
backend="openai_compat",
default_api_base="https://api.openai.com/v1",
supports_max_completion_tokens=True,
exact_model_ids=("gpt-5.6-terra",),
),
ProviderSpec(
name="openai_codex",
Expand Down
104 changes: 79 additions & 25 deletions deeptutor/services/workspace/service.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,10 @@

from deeptutor.core.context import WorkspaceRuntimeContext
from deeptutor.multi_user.context import get_current_user
from deeptutor.services.path_service import get_path_service
from deeptutor.multi_user.partner_access import visible_partners
from deeptutor.multi_user.paths import get_path_service_for_scope
from deeptutor.services.partners.scope import partner_scope
from deeptutor.services.path_service import PathService, get_path_service
from deeptutor.services.settings.interface_settings import atomic_update
from deeptutor.utils.secret_files import ensure_private_directory

Expand Down Expand Up @@ -380,12 +383,18 @@ def _assert_no_symlink_components(
raise WorkspaceError(f"The {operation} path cannot contain symbolic links.")

@staticmethod
def _presentation_root(binding: WorkspaceBinding, *, create: bool = False) -> Path:
def _presentation_root(
binding: WorkspaceBinding,
*,
create: bool = False,
path_service: PathService | None = None,
) -> Path:
"""Return private snapshot storage for one user-scoped workspace id."""

if not re.fullmatch(r"ws_[0-9a-f]{32}", binding.workspace_id):
raise WorkspaceError("Invalid workspace id.")
base = get_path_service().get_runtime_state_dir()
service = path_service if path_service is not None else get_path_service()
base = service.get_runtime_state_dir()
presentations = base / "workspace_presentations"
root = presentations / binding.workspace_id
if create:
Expand Down Expand Up @@ -772,31 +781,76 @@ def export_once(
def resolve_published_item(
self, workspace_id: str, workspace_item_id: str
) -> tuple[Path, WorkspaceItem]:
"""Resolve a published workspace item from the user's or a partner's workspace.

Tries the user's direct workspace bindings first, then searches visible
partner workspace presentations if the item is not found (issue #1267).
"""
if not re.fullmatch(r"wsi_[0-9a-f]{32}", workspace_item_id):
raise WorkspaceError("Invalid workspace item id.")
binding = self.binding_by_id(workspace_id)
root = self._presentation_root(binding)
for directory in (root / "items", root / "blobs"):
if directory.is_symlink():
raise WorkspaceError(
"The private workspace presentation path cannot contain symbolic links."
)
manifest_path = root / "items" / f"{workspace_item_id}.json"
try:
payload = json.loads(manifest_path.read_text(encoding="utf-8"))
item = WorkspaceItem(**payload)
except (OSError, json.JSONDecodeError, TypeError) as exc:
raise WorkspaceError("The presented workspace item is unavailable.") from exc
if item.workspace_id != workspace_id or item.workspace_item_id != workspace_item_id:
raise WorkspaceError("The workspace item manifest is invalid.")
blob = (root / "blobs" / item.sha256).resolve()

candidate_roots: list[Path] = []

# Try user's direct workspace binding first
try:
blob.relative_to(root.resolve())
except ValueError as exc:
raise WorkspaceError("The workspace item path is invalid.") from exc
if not blob.is_file():
raise WorkspaceError("The presented workspace item is unavailable.")
return blob, item
binding = self.binding_by_id(workspace_id)
candidate_roots.append(self._presentation_root(binding))
except WorkspaceError:
pass

# Search visible partner workspace presentations if user's binding misses
# (issue #1267). ``ContentWorkspaceService`` has no per-instance state and
# always resolves through the global ``get_path_service()``, so partner
# lookups must go through the partner's own ``PathService`` directly
# rather than instantiating another service for it.
if not candidate_roots:
for partner in visible_partners():
partner_id = str(partner.get("partner_id") or "").strip()
if not partner_id:
continue
try:
partner_path_service = get_path_service_for_scope(partner_scope(partner_id))
partner_binding = WorkspaceBinding(
workspace_id=workspace_id,
root=partner_path_service.get_workspace_dir().resolve(),
display_name="",
)
candidate_roots.append(
self._presentation_root(partner_binding, path_service=partner_path_service)
)
except WorkspaceError:
continue

if not candidate_roots:
raise WorkspaceError("The workspace is no longer registered for this user.")

# Search candidate roots for the workspace item
for root in candidate_roots:
for directory in (root / "items", root / "blobs"):
if directory.is_symlink():
raise WorkspaceError(
"The private workspace presentation path cannot contain symbolic links."
)
manifest_path = root / "items" / f"{workspace_item_id}.json"
if not manifest_path.is_file():
continue
try:
payload = json.loads(manifest_path.read_text(encoding="utf-8"))
item = WorkspaceItem(**payload)
except (OSError, json.JSONDecodeError, TypeError) as exc:
raise WorkspaceError("The presented workspace item is unavailable.") from exc
if item.workspace_id != workspace_id or item.workspace_item_id != workspace_item_id:
raise WorkspaceError("The workspace item manifest is invalid.")
blob = (root / "blobs" / item.sha256).resolve()
try:
blob.relative_to(root.resolve())
except ValueError as exc:
raise WorkspaceError("The workspace item path is invalid.") from exc
if not blob.is_file():
raise WorkspaceError("The presented workspace item is unavailable.")
return blob, item

raise WorkspaceError("The presented workspace item is unavailable.")


_service = ContentWorkspaceService()
Expand Down
12 changes: 12 additions & 0 deletions tests/services/llm/test_model_overrides.py
Original file line number Diff line number Diff line change
Expand Up @@ -95,3 +95,15 @@ def test_bare_k3_is_exact_and_does_not_capture_unrelated_short_ids() -> None:
assert find_by_model("k3") is moonshot
assert find_by_model("k3-256k") is moonshot
assert find_by_model("sk3") is None


def test_gpt_5_6_terra_exact_model_matching() -> None:
"""GPT-5.6-Terra requires max_completion_tokens; ensure it's properly
recognized and dispatches to OpenAI with supports_max_completion_tokens."""
openai = find_by_name("openai")
assert find_by_model("gpt-5.6-terra") is openai
# Verify that the built kwargs use max_completion_tokens, not max_tokens
payload = _payload("openai", "gpt-5.6-terra")
assert "max_completion_tokens" in payload
assert payload["max_completion_tokens"] == 256
assert "max_tokens" not in payload
58 changes: 58 additions & 0 deletions tests/services/workspace/test_content_workspace.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
from __future__ import annotations

from pathlib import Path
from unittest.mock import patch

import pytest

Expand Down Expand Up @@ -151,6 +152,63 @@ def test_private_presentation_store_rejects_symlink_redirection(
assert not any(outside.iterdir())


def test_resolve_published_item_searches_partner_workspaces(
workspace_service, tmp_path: Path, monkeypatch: pytest.MonkeyPatch
) -> None:
"""Verify that resolve_published_item falls back to partner workspaces (issue #1267).

When a workspace item is not found in the user's direct bindings, the service
should search visible partner workspace presentations before raising an error.
"""
from deeptutor.services.workspace import service as service_module

user_service, paths = workspace_service

user_binding = user_service.current_binding(ensure_output=True)
(user_binding.root / "document.md").write_text("user document", encoding="utf-8")
user_item = user_service.publish(user_binding, [{"path": "document.md", "title": "Doc"}])[0]

blob, _loaded = user_service.resolve_published_item(
user_item.workspace_id, user_item.workspace_item_id
)
assert blob.read_text(encoding="utf-8") == "user document"

# Publish an item into a separate partner workspace tree. ContentWorkspaceService
# has no per-instance state, so building the partner's content means pointing the
# global path service at the partner's own runtime tree while publishing, then
# restoring it before exercising the fallback under test.
partner_paths = PathService(workspace_root=tmp_path / "partner_workspace" / "runtime")
partner_paths.ensure_all_directories()
monkeypatch.setattr(service_module, "get_path_service", lambda: partner_paths)
try:
partner_binding = user_service.current_binding(ensure_output=True)
(partner_binding.root / "partner_doc.md").write_text("partner document", encoding="utf-8")
partner_item = user_service.publish(
partner_binding, [{"path": "partner_doc.md", "title": "Partner Doc"}]
)[0]
finally:
monkeypatch.setattr(service_module, "get_path_service", lambda: paths)

# Mock visible_partners to surface the partner workspace to the user
mock_partner = {"partner_id": "test-partner-1", "name": "Test Partner"}

with patch(
"deeptutor.services.workspace.service.visible_partners",
return_value=[mock_partner],
):
with patch(
"deeptutor.services.workspace.service.get_path_service_for_scope",
return_value=partner_paths,
):
# User should be able to resolve the partner's item through fallback
blob, loaded = user_service.resolve_published_item(
partner_item.workspace_id, partner_item.workspace_item_id
)
assert blob.read_text(encoding="utf-8") == "partner document"
assert loaded.title == "Partner Doc"
assert loaded.workspace_item_id == partner_item.workspace_item_id


def test_private_presentation_subdirectory_rejects_symlink_redirection(
workspace_service, tmp_path: Path
) -> None:
Expand Down
Loading